FileHash checker for Upgrading CM on Air-gapped Networks

During a recent engagement where I was downloading files for an offline CM upgrade, I ran into a number of issues that all stemmed from the redist files not having downloaded properly. In this case, the cab files all downloaded, but only a couple of the exe or msi files. Strangely, if I tried downloading the files individually using the paths in the manifest, it worked.

Once I figured out that it was a handful of missing files, I wanted a way to download and/or check the file hashes before I moved them to an air-gapped network. I threw together the attached script. Just feed it the path you told serviceconnectiontool.exe to download the update packs to.

The right fix is to get the root cause of the failed/blocked downloads, but due to time constraints we were forced to just get the upgrade done and could not wait for the admin(s) responsible for security and network.

Hopefully nobody else needs this, but just in case, here it is.

# Microsoft provides programming examples for illustration only, 
# without warranty either expressed or implied, including, but not 
# limited to, the implied warranties of merchantability and/or 
# fitness for a particular purpose. 
#
# This sample assumes that you are familiar with the programming 
# language being demonstrated and the tools used to create and debug 
# procedures. Microsoft support professionals can help explain the 
# functionality of a particular procedure, but they will not modify 
# these examples to provide added functionality or construct 
# procedures to meet your specific needs. If you have limited 
# programming experience, you may want to contact a Microsoft 
# Certified Partner or the Microsoft fee-based consulting line at 
# (800) 936-5200. 
#
# For more information about Microsoft Certified Partners, please 
# visit the following Microsoft Web site:
# https://partner.microsoft.com/global/30000104

Param(
    [Parameter(Mandatory=$True, Position=0)][String]$OfflineFiles
)

Function Test-FileHash{
    Param(
        [Parameter(Mandatory=$True)][psobject]$FileInfo,
        [Parameter(Mandatory=$True)][String]$Path
    )
    if(test-path "$($redist)\$($FileInfo.CopyName)"){
        if((Get-FileHash "$($redist)\$($FileInfo.CopyName)").Hash -eq $FileInfo.SHA256){write-output $TRUE}
        else{write-output $FALSE}
    }
    else{
        write-host "$($FileInfo.CopyName) not found" -ForegroundColor Red
    }
}

Function Get-FilesFromCab{
    Param(
        [Parameter(Mandatory=$True)][String]$Path
    )
    if(test-path "$($redist)\ConfigMgr.Manifest.cab"){
        expand "$($redist)\ConfigMgr.Manifest.cab" "$($redist)\ConfigMgr.Manifest.xml" -F:ConfigMgr.Manifest.xml|Out-Null
        [xml]$xml = get-content "$($redist)\ConfigMgr.Manifest.xml"
        remove-item "$($redist)\ConfigMgr.Manifest.xml" -Force
    
        $files = @()
        ForEach($Group in $xml.ConfigMgr.Group){
            ForEach($File in $Group.file){$Files += $File}
        }
        $files
    }
    else{write-error "Unable to get file list"}
}

Function Get-CMUpgradeInfo{
    Param(
        [Parameter(Mandatory=$True, Position=0)][String]$Path
    )
    if(test-path "$($Path)\ConfigMgr.LN.Manifest.cab"){
        expand "$($Path)\ConfigMgr.LN.Manifest.cab" "$($Path)\ConfigMgr.LN.Manifest.xml" -F:ConfigMgr.LN.Manifest.xml|Out-Null
        [xml]$xml = get-content "$($Path)\ConfigMgr.LN.Manifest.xml"
        remove-item "$($Path)\ConfigMgr.LN.Manifest.xml" -Force
    
        write-output $xml.ConfigMgr.VersionInfo.ManifestVersion.substring(2)
    }
    else{write-error "Unable to get version info"}
}

$redistFolders = Get-ChildItem $OfflineFiles | Where-Object{$_.Mode -like "d*"} | Select-Object -expand FullName
ForEach($path in $redistFolders){
    $redist = "$($path)\redist"
    write-host "Testing files for version $(Get-CMUpgradeInfo $redist), $($path)" -ForegroundColor Blue
    $Files = Get-FilesFromCab -Path $redist

    ForEach($File in $Files){
        if(Test-FileHash -FileInfo $File -Path $redist){write-host "$($File.CopyName) is a hash match" -ForegroundColor Green}
        else{
            write-host "$($File.CopyName) is missing or is a hash MISMATCH, attempting to download" -ForegroundColor yellow
            Invoke-WebRequest -Uri $File.Source -OutFile "$($redist)\$($File.CopyName)"
            if(Test-FileHash -FileInfo $File -Path $redist){write-host "$($File.CopyName) is a hash match" -ForegroundColor Green}
            else{write-host "$($File.CopyName) is missing or is a hash MISMATCH, attempt to download failed" -ForegroundColor Red}
        }
    }
    Write-Host " "
    Write-Host " "
}