SCCM Distribution Point (DP) automated install via PowerShell

Automation is always good in large or small organizations to minimize error and remove the tediousness of repetitive tasks. This script will install the standard Distribution Point (DP) role on one or multiple site system servers in their assigned site.

This script will run the following task

  • Check if the site server CM admin domain groups were added to the local admin group. If not, the groups will be added.
  • Check if NO_SMS_ON_DRIVE.SMS exist on the C: drive. If not, create it.
  • Check if the content drive exist. If the drive does not exist, the script stops.
  • Install the prerequisites.
  • Check if the Site System for the DP exist. If not, it will be added.
  • Check if the DP role exist. If not, add it.
  • Reports the install status at the end of the script. If the DP is still installing, wait 5 minutes and run the script again and it will report the install status.

How to run the script

The script consist of two files. Add both files to the same folder. Run the script from the CAS or standalone primary.

  • Install-DistributionPoint.ps1
  • DPInputFile.psd1

To run the script, do the following. First, dot source Install-DistributionPoint.ps1 in order to run the function.

. C:\Script\Install-Distributionpoint.ps1

Then runt the Distribution Point install function.

Install-Distributionpoint -InputFile "C:\Script\DPInputFile.psd1"

Save the code below as Install-DistributionPoint.ps1

Function Install-DistributionPoint{
<#
.Synopsis
This script will install the Distribution Point role on one or multiple servers in the specified primary site.
Run from the CAS or standalone primary.  Run the script twice to validate installation if desired.

.Description
 This script will do the following during the provisioning process.
   1.  Check if domain groups were added to local admin group.  If not, add them.
   2.  Check if the content drive exist. If the drive does not exist, stop the script.
   3.  Check if the Site System exist, if not add it.
   4.  Check the DP exist, if not add it.
   5.  Wait until the DP role is installed and report status.

 The input values are stored in DPInputFiles.psd1 - Edit this file with Tenant specific values.

.PARAMETER InputFile
 Specify the path to the INI input file.

.Example
 Install-DistributionPoint -InputFile "C:\Script\DPInputFile.psd1"

.NOTES
 Created on:  12/10/2020
 Created by:  Lynford Heron
 Filename:    Install-DistributionPont.ps1
 Version:     1.0
#>

Param( 
    [string]$InputFile
)

If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
        [Security.Principal.WindowsBuiltInRole] "Administrator"))

    {
        Write-Warning "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!"
        Break
    }

Write-host "Enter the credetial with admin rights on the remote server." -ForegroundColor Yellow
$credential = Get-credential 
$dataFile = Import-PowerShellDataFile $InputFile
$DPDate = [DateTime]::Now.AddYears(10) # Add or reduce the number of years.

# Site configuration
$casSiteCode = $dataFile.ParentSiteCode # Site code 
$ProviderMachineName = $dataFile.ParentSrvName # SMS Provider machine name

# Import the ConfigurationManager.psd1 module 
if($null -eq (Get-Module ConfigurationManager)) {
    If ($ENV:SMS_ADMIN_UI_PATH) {
        Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    }
    Else {
        #If running from jump server, change path below to the install location of the admin console
        $modPath = $dataFile.cminstalldir + "\AdminConsole\bin\ConfigurationManager.psd1"
        Import-Module $modPath
    }
}

# Connect to the site's drive if it is not already present
if($null -eq (Get-PSDrive -Name $casSiteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
    New-PSDrive -Name $casSiteCode -PSProvider CMSite -Root $ProviderMachineName | Out-Null #@initParams
}

# Set the current location to be the site code.
Set-Location "$($casSiteCode):\"

Foreach ($DP in $dataFile.DPInfo)
    {
        $session = New-PSSession -ComputerName $DP.ServerName -Credential $credential
        $prepResults = Invoke-Command -Session $session -ScriptBlock {

            $Cmadmins      = $args[0]
            $CmSiteservers = $args[1]
            $InstallDrive  = $args[2]
            $Domain        = $args[3]

            $results = @{}

            #Create no_sms_on_drive.sms on the C:\drive
            Write-verbose -message "Creating no_sms_on_drive.sms on the c:\ drive" -Verbose
            New-Item c:\no_sms_on_drive.sms -ItemType file -Force
            If (!(Test-Path -Path c:\no_sms_on_drive.sms)) {
                $results['Installno'] = "No"
                Write-Warning "There was a problem creating the file no_sms_on_drive.sms. Fix the problem and run the script again"
                Break
            }

            $date = get-date
            $NewFolder = "c:\ProvisionSrv"
            If (!(Test-Path $NewFolder)) {New-Item c:\ProvisionSrv -ItemType directory}
            $Random = Get-Date -UFormat %Y%m%d_%H%M%S
            $logfile = $NewFolder + "\" + $env:COMPUTERNAME + "_" + $Random + ".log"
            new-item -ItemType file $logfile -Force

            $date = get-date; add-content $logfile ""
            $date = get-date; add-content $logfile " ==================================================================="
            $date = get-date; add-content $logfile "  $date  -  Check if the install partition exist "
            $date = get-date; add-content $logfile " ==================================================================="
            $date = get-date; add-content $logfile ""

            #Check if the install drive exit(E:)
            If (Test-Path $installDrive)
            {
                Write-Verbose -Message "The content partition $($installDrive) is present on $env:COMPUTERNAME." -Verbose; $date = get-date; add-content $logfile "The content partition $($installDrive) is present on $($env:COMPUTERNAME)."
                $results['Installyes'] = "Yes"
            }
            Else
            {
                $results['Installno'] = "No"
                $date = get-date; add-content $logfile "The content partition $($installDrive) is missing on $($env:COMPUTERNAME). This server will not be provisioned until the drive is present:"
                Write-Verbose -Message "The content partition $($installDrive) is missing on $($env:COMPUTERNAME). This server will not be provisioned until the drive is present:" -Verbose
                Break
            }


            $date = get-date; add-content $logfile ""
            $date = get-date; add-content $logfile " ============================================================================================================"
            $date = get-date; add-content $logfile "  $date  -  Check if domain groups exist in local admin group"
            $date = get-date; add-content $logfile " ============================================================================================================"
            $date = get-date; add-content $logfile ""

            #Search if Domain groups exist in local Admin group
            Write-Verbose -message "Checking if the groups $Cmadmins and $CmSiteservers are local admins on $($env:COMPUTERNAME)" -Verbose; $date = get-date; add-content $logfile " $date  -  Checking if $Cmadmins and $CmSiteservers are local admins $($env:COMPUTERNAME)"
            $group = [ADSI]"WinNT://$($env:COMPUTERNAME)/Administrators,Group" 
            $GroupMembers = @($group.Invoke("Members")) | foreach-object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}

            If (!($GroupMembers -contains $Cmadmins)) 
            {
                Write-Host "The domain group $Cmadmins does not exist on $env:COMPUTERNAME. Adding group to local administrators:" -ForegroundColor cyan; $date = get-date; add-content $logfile " $date  -  The domain group $Cmadmins does not exist on $env:COMPUTERNAME. Adding group to local administrators"
                $adgroup = [ADSI]"WinNT://$Domain/$Cmadmins"
                $localgroup = [ADSI]"WinNT://$($env:COMPUTERNAME)/Administrators,Group"
                $localGroup.PSBase.Invoke("Add", $adgroup.PSBase.Path) 

                $GroupMembers2 = @($group.Invoke("Members")) | ForEach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
                }
                If ($GroupMembers2 -contains $Cmadmins) 
                {
                    Write-Host "The missing group $Cmadmins was added" -ForegroundColor Yellow; $date = get-date; add-content $logfile " $date  -  The missing group $Cmadmins added"
                    $results['Installyes'] = "Yes"
                } 
                Else
                {
                    $results['Installno'] = "No"
                    $date = get-date; add-content $logfile " $date  -  ERROR: There was a problem adding the domain group $Cmadmins to the local administrators group on $env:COMPUTERNAME. Check the ProvisionPDP log and work to resolve"
                    Write-Error "There was a problem adding the domain $Cmadmins to the local administrators group on $env:COMPUTERNAME. Check the ProvisionPDP log and work to resolve"  -ErrorAction Stop
                }

            }

            If (!($GroupMembers -contains $CmSiteservers)) 
            {
                Write-Host "The domain group $CmSiteservers does not exist on $env:COMPUTERNAME. Adding group to local administrators:" -ForegroundColor Yellow #; #$date = get-date; #add-content $logfile " $date  -  The domain group $CmSiteservers does not exist on $env:COMPUTERNAME. Adding group to local administrators"
                $adgroup = [ADSI]"WinNT://$Domain/$CmSiteservers"
                $localgroup = [ADSI]"WinNT://$($env:COMPUTERNAME)/Administrators,Group"
                $localGroup.PSBase.Invoke("Add", $adgroup.PSBase.Path) 

                $GroupMembers2 = @($group.Invoke("Members")) | foreach-object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
                }
        
                If ($GroupMembers2 -contains $CmSiteservers) 
                {
                    Write-Host "The missing group $CmSiteservers was added" -ForegroundColor Yellow; $date = get-date; add-content $logfile " $date  -  The missing group $CmSiteservers was added"
                    $results['Installyes'] = "Yes"
                } 
                Else 
                {
                    $results['Installno'] = "No"
                    add-content $logfile " $date  -  ERROR: There was a problem adding the domain group $CmSiteservers to the local administrators group on $env:COMPUTERNAME. Check the script and event log and work to resolve"
                    Write-Error "There was a problem adding the domain group $CmSiteservers to the local administrators group on $env:COMPUTERNAME. Check the script and event log and work to resolve." -ErrorAction Stop
                }
            } 
            If ($GroupMembers -contains $Cmadmins -and $GroupMembers -contains $CmSiteservers)
            {
                Write-Host "Domain Groups $Cmadmins and $CmSiteservers already exist on $env:COMPUTERNAME" -ForegroundColor Yellow; $date = get-date; add-content $logfile " $date  -  Domain Groups CMAdmins and $CmSiteservers already exist on $env:COMPUTERNAME"
                $results['Installyes'] = "Yes"
            }


            #Install Windows features - The commands below with also install IIS and management console as well
            Write-Verbose -Message "Installing the required IIS components if required. Please wait..." -Verbose
            Install-WindowsFeature NET-Framework-Core | Out-Null
            Install-WindowsFeature RDC | Out-Null
            Install-WindowsFeature Web-ISAPI-Ext,Web-Windows-Auth,Web-Metabase,Web-WMI | Out-Null

            #If Windows firewall is on, add exceptions. 
            If((Get-NetFirewallProfile -Name Domain).Enabled)
            {  Write-host "Windows Firewall is enabled.  Adding exceptions" -ForegroundColor Yellow
                New-NetFirewallRule -DisplayName 'HTTP(S) Inbound' -Profile Domain -Direction Inbound -Action Allow -Protocol TCP -LocalPort @(80,443) -Group "For SCCM DP"
                New-NetFirewallRule -DisplayName 'SMB DP Inbound' -Profile Domain -Direction Inbound -Action Allow -Protocol TCP -LocalPort 445 -Group "For SCCM DP"
                New-NetFirewallRule -DisplayName 'Multicast Protocol Inbound' -Profile Domain -Direction Inbound -Action Allow -Protocol TCP -LocalPort 63000-64000 -Group "For SCCM DP"
            }
            Else
            {  Write-host "Windows Firewall domain profile is disabled" -ForegroundColor Cyan}

            return $results

        } -ArgumentList $dataFile.SG_CM_Admins, $dataFile.SG_CM_SiteServers, $DP.InstallDrive, $datafile.Domain


        $date = get-date
        $LogFolder = "c:\ProvisionSRV"
        If (!(Test-Path $LogFolder)) {New-Item $LogFolder -ItemType directory}
        $Random2 = Get-Date -UFormat %Y%m%d_%H%M%S
        $logfile = $LogFolder + "\" + "DPInstall" + "_" + $Random2 + ".log"
        new-item -ItemType file $logfile -Force
        Write-Host "The log file $logfile was created" -ForegroundColor Yellow; $date = get-date; add-content $logfile "$date  -  The log file $logfile was created"

        $date = get-date; add-content $logfile " =================================================================="
        $date = get-date; add-content $logfile "  $date  -  Installing Distribution Point role"
        $date = get-date; add-content $logfile " =================================================================="
        $date = get-date; add-content $logfile ""

        If(!($prepResults.Installno -eq "No"))
        {
            $installDP = @{}
            if((Get-PSDrive -Name $($DP.Sitecode) -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
            New-PSDrive -Name $($DP.SiteCode) -PSProvider CMSite -Root $($DP.PriServer) | Out-Null
            }
            Set-Location "$($DP.Sitecode):\" | Out-Null

            $SSresults = (Get-CMSiteSystemServer -SiteSystemServerName $($DP.ServerName)).NetworkOSPath 
            If ($SSresults) {
                Write-Host "SCCM Site System $SSresults already exit.  Checking if the Distribution Point role already exist." -ForegroundColor Yellow; $date = get-date; add-content $logfile "$date  -  SCCM Site System $SSresults already exit. Checking if the PDP role already exist."
                $DPresults = (Get-CMDistributionPoint -SiteSystemServerName $($DP.ServerName)).NetworkOSPath
                If ($DPresults)
                {
                    Write-Verbose -Message "The SCCM Distribution Point $($DP.ServerName) already exist." -Verbose; add-content $logfile "$date  -  The SCCM Distribution Point $DPresults already exist."
                    $installDP['Installyes'] = "Yes"
                }
                Else 
                {
                    Write-Verbose -Message "The Distribution Point role does not exist on $($DP.ServerName). Adding DP role, please wait..." -Verbose
                    $date = get-date; add-content $logfile "$date  -  The SCCM Distribution Point $($DP.ServerName) does not exist. Adding DP role, please wait..."
                    $driveLeter = $DP.InstallDrive.Replace(":","")
                    Try
                    {
                        Add-CMDistributionPoint -SiteSystemServerName $($DP.ServerName) -SiteCode $($DP.SiteCode) -CertificateExpirationTimeUtc $DPDate -InstallInternetServer -ClientConnectionType $($DP.ConnectionType) -Description $($DP.DPdesc) -MinimumFreeSpaceMB $($DP.FreespaceMB) -PrimaryContentLibraryLocation $driveLeter -PrimaryPackageShareLocation $driveLeter -EnableValidateContent | Out-Null
                        Start-Sleep -s 2
                        $DPresults2 = (Get-CMDistributionPoint -SiteSystemServerName $($DP.ServerName)).NetworkOSPath
                        If ($DPresults2)
                        {
                            $installDP['Installyes'] = "Yes"
                            Write-Host "The SCCM Distribution Point $($DP.ServerName) installation process started." -ForegroundColor Yellow; add-content $logfile "$date  -  The SCCM Distribution Point $DPresults installation process started."
                        }
                    }
                    Catch
                    {
                        $installDP['Installno'] = "No"
                        Write-Error "There was a problem executing the add DP command - (CM1)." -ErrorAction Stop
                    }
                    Start-Sleep -s 7

                }
            }
            Else 
            {
                Write-Host "SCCM Site System $($DP.ServerName) does not exist. Adding Server as a CM site system.  Please wait..."
                $date = get-date; add-content $logfile "$date  -  SCCM Site System $($DP.ServerName) does not exist. Adding Server as a CM site system.  Please wait..."
                Try
                {
                    New-CMSiteSystemServer -ServerName $($DP.ServerName) -SiteCode $($DP.SiteCode) -AccountName $null | Out-Null -ErrorAction SilentlyContinue -ErrorVariable err
                }
                Catch
                {
                    $installDP['Installno'] = "No"
                    Write-Error "The was an error adding the site system - (CM2)." -ErrorAction Stop
                }

                Start-Sleep -s 7
                $SSresults2 = (Get-CMSiteSystemServer -SiteSystemServerName $($DP.ServerName)).NetworkOSPath
                If ($SSResults2)
                {
                    Write-Host "Server $($DP.ServerName) was added as a SCCM Site System. Adding Distribution Point role, please wait..." -ForegroundColor Yellow; $date = get-date; add-content $logfile "$date  -  Server $SSname was added as a SCCM Site System. Adding PDP role, please wait..."
                    $driveLeter = $DP.InstallDrive.Replace(":","")
                    Try
                    {
                        Add-CMDistributionPoint -SiteSystemServerName $($DP.ServerName) -SiteCode $($DP.SiteCode) -CertificateExpirationTimeUtc $DPDate -InstallInternetServer -ClientConnectionType $($DP.ConnectionType) -Description $($DP.DPdesc) -MinimumFreeSpaceMB $($DP.FreespaceMB) -PrimaryContentLibraryLocation $driveLeter -PrimaryPackageShareLocation $driveLeter -EnableValidateContent | Out-Null
                        $DPresults3 = (Get-CMDistributionPoint -SiteSystemServerName $($DP.ServerName)).NetworkOSPath
                        If ($DPresults3)
                        {
                            $installDP['Installyes'] = "Yes"
                            Write-Host "The SCCM Distribution Point $($DP.ServerName) installation process started." -ForegroundColor Yellow; add-content $logfile "$date  -  The SCCM Distribution Point $DPresults installation process started."
                        }
                    }
                    Catch
                    {
                        $installDP['Installno'] = "No"
                        Write-Error "The was a problem adding the DP role on $($DP.ServerName) -(CM3)" -ErrorAction Stop
                    }
                    
                    Start-Sleep -s 7
                } 
            }
        }

        If(!($installDP.Installno -eq "No"))
        {
            $strcount = 0
            $date = get-date
            Write-Host "$date - Starting Do until loop.  Checking if the DP role is installed on $($DP.ServerName)."; $date = get-date; add-content $logfile "$date  -  Starting Do until loop.  Checking if the DP is installed on $($DP.ServerName)."
            $DPlog = $DP.DPlog
            Do {
                Start-Sleep -s 30
                $strcount += 30
                $DPinstall = Invoke-Command -ComputerName $DP.serverName -Credential $credential -ScriptBlock {$DPinstallLog = $args[0]; Get-Content -ErrorAction SilentlyContinue -ErrorVariable err $DPinstallLog | select-object -Last 10 | where-object {$_ -match 'has been added to content library successfully' -or $_ -match 'A DP usage gathering task has been registered successfully'}} -ArgumentList $DP.DPlog
                If ($err) {Write-Host "The file $DPlog does not exist as yet." -ForegroundColor Cyan; add-content $logfile "$date  -  The file $DPlog does not exist as yet."}
                Write-Host "$strcount seconds - still checking... " -ForegroundColor Yellow; $date = get-date; add-content $logfile "$date  -  $strcount minutes - still checking... " 
            } Until ($Null -ne $DPinstall -or $strcount -eq 60)
            If ($DPinstall) {
                Write-host "DP was installed successfully." -ForegroundColor Yellow; $date = get-date; add-content $logfile "$date  -  DP role was installed successfully on $($DP.ServerName)"
                Start-Sleep -s 5
            }
            If ($strCount -eq 60) {
                Write-Verbose -Message "It has been over 1 minute and the DP is still installing. Wait 5 minutes and try again, or check the smsdpProv.log." -Verbose; add-content $logfile "$date  -   It has been over 60 seconds and the DP $($DP.ServerName) is still installing. Do a manual check to confirm."
            }
       }

    }
    Get-PSSession | Remove-PSSession
}

The code for the input file is displayed below. Copy and save as DPInputFile.psd1

@{
    ParentSiteCode    = 'CAS' # Top level site code
    ParentSrvName     = 'ParentSrv.Domain.com' #Top level site server
    CMinstallDir      = 'E:\SCCM'
    SG_CM_Admins      = 'SG_CM_Admins'
    SG_CM_SiteServers = 'SG_CM_SiteServers'
    Domain            = 'Domain.com' #Change this to the domain of the Admin and Server groups if different from the domain of the computers.
    DPInfo = @(
        @{
            SiteCode          = 'PR1'
            ServerName        = 'DP1.Domain.com' #Name of distribution point server
            PriServer         = 'PR1.Domain.com'
            DPdesc            = 'New York Distribution Point'
            ConnectionType    = 'Intranet' #ConnectionType values are Intranet, Internet or InternetAndIntranet
            CommunicationType = 'HTTP' #CommunicationType values are HTTP or HTTPS
            DPLog             = 'E:\SMS_DP$\sms\logs\smsdpProv.log'
            InstallDrive      = 'E:'
            FreespaceMB       = '5000'
        }
        @{
            SiteCode          = 'PR1'
            ServerName        = 'DP2.Domain.com' #Name of distribution point server
            PriServer         = 'PR1.Domain.com'
            DPdesc            = 'Queens, NY MP'
            ConnectionType    = 'Intranet' #ConnectionType values are Intranet, Internet or InternetAndIntranet
            CommunicationType = 'HTTP' #CommunicationType values are HTTP or HTTPS
            DPLog             = 'E:\SMS_DP$\sms\logs\smsdpProv.log'
            InstallDrive      = 'E:'
            FreespaceMB       = '5000'
        }
    )
}

Author