Create Prestage Content Files for All Distributed Data
By Jesse Walter
Published August 12, 2015
Estimated Reading Time: 5 minutes

Oh, hey! I didn’t see you there. Come see what I’m doing.

 

I had a client request to create prestage content for every production package, application, OS image, etc. They are in the midst of a project in which they are replacing most of their Distribution Points, but the pipe into the new DPs will be limited. Therefore, standard distribution methods will not cut it. So, we elected to enable the DPs for prestage content and load it all once onsite.

 

Naturally, we could have selected each individual package, application, etc. and used the console to create the .pkgx files, but where’s the fun in that? This has to be easily scriptable, especially with the Publish-CMPrestageContent cmdlet, right? Wellllllllllllllll…

 

I scoured the web for any scripts that may already exist. After all, there’s no need to recreate the wheel. I came across this post by the well-known David O’Brien. However, I need wanted something a little more simplistic, with fewer parameters; I’m just trying to grab everything available and create content files. Also, the script was a little outdated (required you to run in an x86 PS session), so I figured I should take a stab at writing my own to fit my client’s needs.

 

The script (below) does the following:

 

  • Accepts two parameters, “DestinationPath” and “SourceDP” (I plan on adding functionality later to allow for single Prestage Content file creation)
  • Uses WMI and the namespace root\SMS\site_<site code> and various classes to gather all packages, driver packages, OS images (did not add OS installers yet), boot images, etc.
    • To gather applications, I instead use the Get-CMApplication cmdlet
  • Creates a couple directories based on your input
    • Root directory based on the parameter
    • Content directory underneath defined path in which the .pkgx files are written
  • Copies extractcontent.exe (needed to unpack on the destination DPs) to the defined destination directory
  • Creates a batch file with the necessary command parameters to run the extractcontent.exe and places it on the defined destination directory
  • Gathers all available content for Prestage Content creation and creates files based on availability on the parameter-defined source DP
    • Packages
    • Applications
    • Software Update Deployment Packages
    • Driver Packages
    • OS Images
    • Boot Images
  • Writes to a log file titled “PrestageContent_<short date and time>.log”, which exists in c:\windows\temp
  • Cooks you breakfast in the morning

 

NOTE: This script must be run on a machine with the console installed (i.e. full access to the CM module). Also, be sure that the content of which you would like to create prestage files exists on the source DP (script will not fail, but no file will be created unless content has been distributed). Also, pardon the blog formatting… still getting used to this plugin.

 


[CmdletBinding()]

param(

[Parameter(Mandatory=$TRUE)]

[string]$DestinationPath,

[Parameter(Mandatory=$TRUE)]

[string]$SourceDP

)

## Gets date and formates it to short date and time

$Date = Get-Date -Format G

## Gets current working directory for reverting at end of script

$CurrentPath = Convert-Path .

## Creates logfile

$LogSuffix = $Date -replace " ", "" -replace "/", "" -replace ":", "" | % {$_.Substring(0,$_.length - 2)}

$LogFile = "$env:windir\temp\PrestageContent_$LogSuffix.log"

## Creates a logfile that will be written to during the process

Function Write-Log ($LogText)

{

Add-Content $LogFile "$(Get-Date) - $LogText"

Write-Output "$Date - $LogText"

}

Write-Log "Log file located at $LogFile"

## Tests required connection to source DP

Write-Log "Testing connection to $SourceDP"

if (!(Test-Connection $SourceDP -Count 1 -ErrorAction SilentlyContinue)) {

Write-Log "Cannot connect to $SourceDP. Please check name and try again."

exit 1

}

## Ensures source DP is always FQDN

Write-Log "Qualifying $SourceDP"

$SourceDP = ([System.Net.Dns]::GetHostByName($SourceDP)).HostName

Write-Log "The FQDN of the source DP is $SourceDP"

## Imports the configuration manager module

Function Import-CMModule {

Write-Log "Imorting configuration manager module"

Try {

Import-Module -Name "$(split-path $Env:SMS_ADMIN_UI_PATH)\ConfigurationManager.psd1"

$global:Site = Get-PSDrive -PSProvider CMSite

CD "$($Site):"

Write-Log "Site PSDrive now $Site"

Set-Variable -Name Site -Value $Site.Name

}

Catch {

Write-Log "Cannot import the Configuration Manager module. $_"

exit 1

}

}

Import-CMModule

## Defines error action, location of extractcontent.exe, and arrays for item type variables

$ErrorActionPreference = "SilentlyContinue"

$ExtractContentApp = (Split-Path $env:SMS_LOG_PATH) + "\bin\x64\extractcontent.exe"

$Packages = @()

$DriverPackages = @()

$SoftwareUpdatesPackages = @()

$Applications = @()

$OSImages = @()

$BootImages = @()

## Verifies site variable exists

if (($Site -eq "") -or ($site -eq $null)) {

Write-Log "Failed to write site variable; exiting script"

exit 1

}

## Sets variables for each item type we are importing based on data from WMI

$Packages = gwmi -class SMS_Package -Namespace root\sms\site_$Site

$DriverPackages = gwmi -Class SMS_DriverPackage -Namespace root\sms\site_$Site

$SoftwareUpdatesPackages = gwmi -Class SMS_SoftwareUpdatesPackage -Namespace root\sms\site_$Site

$Applications = Get-CMApplication

$OSImages = gwmi -class SMS_ImagePackage -Namespace root\sms\site_$Site

$BootImages = gwmi -Class SMS_BootImagePackage -Namespace root\sms\site_$Site

## Create Destination Path if does not exist

if (!(Test-Path $DestinationPath)) {

Write-Log "Destination path does not exist. Creating $DestinationPath"

New-Item -ItemType directory -Path $DestinationPath | Out-Null

}

##Create content directory

$ContentDir = if (!(Test-Path "$DestinationPath\Content")){New-Item -ItemType directory -Path "$DestinationPath\Content" | Out-Null; "$DestinationPath\Content"} else {"$DestinationPath\Content"; Write-Log "Content directory exists"}

## Copy extractcontent.exe to destination path for unpacking later

Write-Log "Copying $ExtractContentApp to $DestinationPath"

Copy-Item -Path $ExtractContentApp -Destination $DestinationPath

## Create batch file for unpacking and place in destination folder

Write-Log "Creating batch file for unpacking content on destination server"

$batch = "%~dp0extractcontent.exe /P:%~dp0Content /F" | Out-File -FilePath "$DestinationPath\UnpackContent.bat" -Append -Encoding ascii

## Create prestaged content for software packages

Write-Log "Discovered $(@($Packages).count) packages. Begin processing packages..."

ForEach ($Package in $Packages) {

Write-Log "Creating file for $($Package.name)"

Try {

Publish-CMPrestageContent -PackageId $($Package.PackageID) -FileName $(Join-Path $ContentDir "$($Package.name).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($Package.name).pkgx"))) {

Write-Log "Warning: issue creating $($Package.name). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($Package.name)"

}

}

Catch {

Write-Log "Failed to copy $($Package.name); $_"

}

}

## Create prestaged content for driver packages

Write-Log "Discovered $(@($DriverPackages).count) driver packages. Begin processing driver packages..."

ForEach ($DriverPackage in $DriverPackages) {

Write-Log "Creating file for $($DriverPackage.name)"

Try {

Publish-CMPrestageContent -DriverPackageId $($DriverPackage.PackageID) -FileName $(Join-Path $ContentDir "$($DriverPackage.name).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($DriverPackage.name).pkgx"))) {

Write-Log "Warning: issue creating $($DriverPackage.name). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($DriverPackage.name)"

}

}

Catch {

Write-Log "Failed to copy $($DriverPackage.name); $_"

}

}

## Create prestaged content for software updates packages

Write-Log "Discovered $(@($SoftwareUpdatesPackages).count) software updates packages packages. Begin processing software updates packages..."

ForEach ($SoftwareUpdatesPackage in $SoftwareUpdatesPackages) {

Write-Log "Creating file for $($SoftwareUpdatesPackage.name)"

Try {

Publish-CMPrestageContent -DeploymentPackageId $($SoftwareUpdatesPackage.PackageID) -FileName $(Join-Path $ContentDir "$($SoftwareUpdatesPackage.name).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($SoftwareUpdatesPackage.name).pkgx"))) {

Write-Log "Warning: issue creating $($SoftwareUpdatesPackage.name). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($SoftwareUpdatesPackage.name)"

}

}

Catch {

Write-Log "Failed to copy $($SoftwareUpdatesPackage.name); $_"

}

}

## Create prestaged content for applications

Write-Log "Discovered $(@($Applications).count) applications. Begin processing applications..."

ForEach ($Application in $Applications) {

Write-Log "Creating file for $($Application.LocalizedDisplayName)"

Try {

Publish-CMPrestageContent -ApplicationName $($Application.LocalizedDisplayName) -FileName $(Join-Path $ContentDir "$($Application.LocalizedDisplayName).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($Application.LocalizedDisplayName).pkgx"))) {

Write-Log "Warning: issue creating $($Application.LocalizedDisplayName). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($Application.LocalizedDisplayName)"

}

}

Catch {

Write-Log "Failed to copy $($Application.LocalizedDisplayname); $_"

}

}

## Create prestaged content for OS images

Write-Log "Discovered $(@($OSImages).count) OS images. Begin processing OS Images..."

ForEach ($OSImage in $OSImages) {

Write-Log "Creating file for $($OSImage.name)"

Try {

Publish-CMPrestageContent -OperatingSystemImageId $($OSImage.PackageID) -FileName $(Join-Path $ContentDir "$($OSImage.name).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($OSImage.name).pkgx"))) {

Write-Log "Warning: issue creating $($OSImage.name). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($OSImage.name)"

}

}

Catch {

Write-Log "Failed to copy $($OSImage.name); $_"

}

}

## Create prestaged content for boot images

Write-Log "Discovered $(@($BootImages).count) boot images. Begin processing boot images..."

ForEach ($BootImage in $BootImages) {

Write-Log "Creating file for $($BootImage.name)"

Try {

Publish-CMPrestageContent -BootImageID $($BootImage.PackageID) -FileName $(Join-Path $ContentDir "$($BootImage.name).pkgx") -DistributionPointName $SourceDP | Out-Null

if (!(Test-Path $(Join-Path $ContentDir "$($BootImage.name).pkgx"))) {

Write-Log "Warning: issue creating $($BootImage.name). Please try again later or create manually."

}

else {

Write-Log "Successfully created $($BootImage.name)"

}

}

Catch {

Write-Log "Failed to copy $($BootImage.name); $_"

}

}

cd $CurrentPath

Write-Log "Prestaged content creation complete"

Article By Jesse Walter
Jesse Walter is a Partner with Model Technology Solutions and the Vice President of Research and Development. He has an extensive background in Microsoft endpoint management tools, such as Microsoft Endpoint Configuration Manager and Intune, as well as a strong foundation in the Microsoft 365 Defender stack. Additionally, he enjoys automating repeatable operational tasks using PowerShell, and has developed several security tools using C#.

Related Posts