PowerShell - Automate SCCM PreReq Install

Written on January 5, 2019

This is a part 2 to my original post about setting up SCCM on a server disconnected from the internet.

That being said, using this automation does not mean it will not speed up your SCCM deployment on an internet connected server. There is nothing stopping you from running the download script on your SCCM server, then running this install script using the downloaded files directly. If you have to build SCCM environments often, storing and keeping the downloaded files in a zip file would make the install process much faster. All you need then is to copy the files and run the install script.

I should mention that the ActiveDirectory prerequisites (creating the System Management container and extending the Schema) are not included in this script. I might cover that in a future post.

What the Script Will Do


The script is going to install all the required Windows Features for SCCM. It will install the required components from the ADK. Since SCCM requires .NetFramework 3.5, and that component is removed from Windows, you will need to mount the Windows ISO to the computer to install that specific feature. You will need to specify the Windows source path in the script to get that feature installed. The script will skip this feature if you have not specified that parameter.

The Script


Examples for how to use it are below the code. It is recommended to copy this code to a text editor, then save it as a ps1 file (SccmPreReqInstall.ps1 for example). You can then run it in an elevated PowerShell session.

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$true,Position=0)]
  [String]$prereqSourcePath,

  [Parameter(Position=1)]
  [ValidateScript({Test-Path -Path $_})]
  [String]$windowsMediaSourcePath
)

Function isAdministrator {
  $wid=[Security.Principal.WindowsIdentity]::GetCurrent()
  $principal=New-Object -TypeName Security.Principal.WindowsPrincipal `
    -ArgumentList $wid
  $adminRole=[Security.Principal.WindowsBuiltInRole]::Administrator

  return $principal.IsInRole($adminRole)
}

Function GetADKInstalledFeatures {
  [CmdletBinding()]
  Param()

  $kitsRegPath='HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits'

  Try{
    $winKitsInstall=Test-Path -Path $kitsRegpath
    if(!$winKitsInstall){
      Write-Error "[$kitsRegPath] does not exist. ADK not installed." `
        -ErrorAction Stop
    }
    $installRoots=Get-ChildItem -Path "$kitsRegPath\Installed Roots" `
      -ErrorAction Stop

    if(!$installRoots){
      Write-Error "Install Roots key not found.  ADK features not installed." `
        -ErrorAction Stop
    }
    $installOptPath="$($installRoots[0].PSPath)\Installed Options"
    $installOpt=Get-Item -Path $installOptPath -ErrorAction Stop
    
    $installOptHash=New-Object -TypeName hashtable
    foreach($option in $installOpt.Property){
      $installOptHash.Add($option,$installOpt.GetValue($option))
    }
    Write-Output $installOptHash
  }Catch{
    Write-Error $_
  }
}

Function WaitForProcessEnd {
  [CmdletBinding()]
  Param(
    [String]$processName,

    [String]$msg
  )
  $processRunning=$true
  Write-Host $msg -NoNewline
  Do{
    Write-Host '.' -NoNewline
    Start-Sleep -Seconds 30
    $process=Get-Process -Name $processName `
      -ErrorAction SilentlyContinue
    if(!$process){
      $processRunning=$false
    }
  }While($processRunning)
}

Try{
  Write-Verbose "Admin?"
  $isAdmin=isAdministrator
  if(!$isAdmin){
    Write-Error "Not running as Admin. Run with Administrative permissions" `
      -ErrorAction Stop
  }

  Write-Verbose "Validating PreReq files"

  $prereqTest=Test-Path -Path $prereqSourcePath
  if(!$prereqTest){
    Write-Error "PreReq Source Path is invalid" `
      -ErrorAction Stop
  }

  $sccmPreReqPath=Join-Path -Path $prereqSourcePath -ChildPath prereq
  $adkPath=Join-Path -Path $prereqSourcePath -ChildPath adk
  $adkPePath=Join-Path -Path $prereqSourcePath -ChildPath adkPeAddon

  $prereqFolders=@(
    $sccmPreReqPath,
    $adkPath,
    $adkPePath
  )

  $peAddonPresent=$true
  $peAddonRequired=$false
  [version]$adkVersionNoPE='10.1.17763.1'

  foreach($folder in $prereqFolders){
    $folderExist=Test-Path -Path $folder
    if(!$folderExist){
      switch -Wildcard ($folder) {

        '*adkPeAddon' {

          $peAddonPresent=$false
          continue
        }

        '*adk' {

          Write-Error "Required PreReq folder not found [$folder]" `
            -ErrorAction Stop
        }

        Default {
          $warn=[String]::Format(
            "The folder {0} was not found. {1}.  {2}",
            $folder,
            "This may cause issues during sccm install",
            "Install will continue..."
          )
          Write-Warning $warn
        }
      } # End switch
    } # End folder test
  } # End folder loop


  ### ADK and WinPE Addon Validation
  Write-Verbose "Validating ADK Status"

  $adkMandatoryFeatureList=@(
    'OptionId.DeploymentTools',
    'OptionId.UserStateMigrationTool',
    'OptionId.WindowsPreinstallationEnvironment'
  )
  $requiredAdkFeatureList=New-Object -TypeName Collections.ArrayList

  $adkInstallHash=GetADKInstalledFeatures -ErrorAction SilentlyContinue
  foreach($feature in $adkMandatoryFeatureList){
    if($adkInstallHash.$feature -ne 1){
      Write-Verbose "[$feature] required"
      [void]$requiredAdkFeatureList.Add($feature)
    }
  }
  if($requiredAdkFeatureList){
    Write-Verbose "ADK features missing... Validating ADKSetup files"
    $adk=Get-Item -Path "$adkPath\adksetup.exe" -ErrorAction Stop
    [version]$adkVersion=$adk.VersionInfo.FileVersion

    if($adkVersion -ge $adkVersionNoPE){
      $peAddonRequired=$true

      if(!$peAddonPresent){
        $emsg=[String]::Format(
          "Your version of ADK requires a WinPE Addon. {0}. {1}.",
          "The addon is not present",
          "Unable to install ADK"
        )
        Write-Error $emsg -ErrorAction Stop
      }
      $adkPeSetupFile=Test-Path -Path "$adkPePath\adkwinpesetup.exe"
      if(!$adkPeSetupFile){
        $emsg=[String]::Format(
          "Your version of ADK requires a WinPE Addon. {0}. {1}.",
          "The adkwinpesetup.exe file is not present",
          "Unable to install ADK"
        )
        Write-Error $emsg -ErrorAction Stop
      }
    }
  }else{
    Write-Verbose "All mandatory ADK features present"
  }
  ### END of ADK and WinPE Addon Validation


  ### Windows Feature Validation

  if(!(Get-Module -Name ServerManager -ErrorAction SilentlyContinue)){
    Import-Module -Name ServerManager -ErrorAction Stop
  }
  $features=Get-WindowsFeature -ErrorAction Stop

  $winFeatureList=@(
    'NET-HTTP-Activation',
    'NET-Non-HTTP-Activ',
    'NET-Framework-45-ASPNET',
    'NET-WCF-HTTP-Activation45',
    'NET-WCF-TCP-PortSharing45',
    'BITS',
    'BITS-IIS-Ext',
    'RDC',
    'Web-Server',
    'Web-Common-Http',
    'Web-Default-Doc',
    'Web-Dir-Browsing',
    'Web-Http-Errors',
    'Web-Static-Content',
    'Web-Http-Redirect',
    'Web-Health',
    'Web-Http-Logging',
    'Web-Request-Monitor',
    'Web-Http-Tracing',
    'Web-Security',
    'Web-Filtering',
    'Web-Basic-Auth',
    'Web-CertProvider',
    'Web-IP-Security',
    'Web-Url-Auth',
    'Web-Windows-Auth',
    'Web-App-Dev',
    'Web-Net-Ext',
    'Web-Net-Ext45',
    'Web-ISAPI-Ext',
    'Web-ISAPI-Filter',
    'Web-Includes',
    'Web-Ftp-Server',
    'Web-Ftp-Service',
    'Web-Mgmt-Tools',
    'Web-Mgmt-Console',
    'Web-Mgmt-Compat',
    'Web-Metabase',
    'Web-Lgcy-Mgmt-Console',
    'Web-Lgcy-Scripting',
    'Web-WMI',
    'Web-Scripting-Tools',
    'Web-Mgmt-Service',
    'RSAT-Feature-Tools',
    'RSAT-Bits-Server'
  )

  # NET Framework Core is removed from recent OS's.
  ## Must be installed from Windows install media
  $netfxCoreFeature='NET-Framework-Core'
  $netfx3=$features | Where-Object {$_.Name -eq $netfxCoreFeature}
  if(!($netfx3.Installed)){
    if(!($windowsMediaSourcePath)){
      $mediaWarn=[String]::Format(
        "WindowsFeature: {0}. {1} is required for this installation. {2}.",
        "[$netfxCoreFeature] is not installed",
        "You will need to complete this manually before you can install SCCM."
      )
      Write-Warning $mediaWarn
    }else{
      Write-Verbose "Installing Windows feature: [$netfxCoreFeature]"
      Install-WindowsFeature -Name $netfxCoreFeature `
        -Source $windowsMediaSourcePath `
        -ErrorAction Stop
    }
  }
  ### END Windows Feature Installation

  Write-Verbose "Installing remaining Windows features"
  Install-WindowsFeature -Name $winFeatureList -ErrorAction Stop


  if(!$requiredAdkFeatureList){
    Write-Verbose "ADK Install not required"
    return
  }

  $winPeFeature='OptionId.WindowsPreinstallationEnvironment'

  if(!($requiredAdkFeatureList.Contains($winPeFeature))){
    $peAddonRequired=$false
  }

  if($peAddonRequired){
    $requiredAdkFeatureList.Remove($winPeFeature)
  }

  if($requiredAdkFeatureList){
    $adkFeature=$($requiredAdkFeatureList -join ' ')
    & "$adkPath\adksetup.exe" /ceip off /features $adkFeature /quiet

    WaitForProcessEnd -processName adksetup `
      -msg "Installing ADK"
  }
  if($peAddonRequired){
    & "$adkPePath\adkwinpesetup.exe" /features $winPeFeature /quiet

    WaitForProcessEnd -processName adkwinpesetup `
      -msg "Installing ADK PE Addon"
  }
}Catch{
  Write-Error $_
}

Example 1

SccmPreReqInstall.ps1 -prereqSourcePath . -windowsMediaSourcePath D:\sources\sxs\

In this example, we are using the current directory for the prereqSourcePath. By using the -windowsMediaSourcePath parameter, we are telling the script to install .NetFramework 3.5 using the Windows installer source media under D:\sources\sxs. This would have required us to mount the Windows installer ISO prior to running the script.

Example 2

SccmPreReqInstall.ps1 -prereqSourcePath C:\temp\sccm -Verbose

In this example, we specify that the ADK prerequisites are in the C:\temp\sccm folder. Since we have not specified -windowsMediaSourcePath, the script will not install .Net Framework 3.5, but it will notify us if it is not installed so we can manually install it later.

What is Left


The last bit of setup that is still needed for the SCCM install, is the Active Directory portion. You will still need to create the System Management container in AD, and setup the permissions on that container. I have made a post / script to do exactly this here. Lastly, you will need to extend the schema.

When you get to running the SCCM installer, just remember to choose the 'prereq' folder (in the prereq source path) when you get to the Prerequisite Downloads step.


Thanks for reading

PS> exit