• Uncategorized
  • 0

Patcha offline VHD-filer

Många tycker inte det är så upphetsande att uppdatera sina maskiner, desto mindre sexigt är det att uppdatera sina template maskiner för snabbare utskick av nya maskiner.

Visst kan man starta upp maskinen och göra det manuellt eller som bäst via WSUS, men om man inte behöver starta maskinen utan bara uppdatera själva hårddisken (VHD, VHDX) utan att boota igång den, visst vore det underbart?

Det går! Och jag har lånat en hel del av scriptet från en annan som gjort detta otroliga arbete vilket du kan se i sin helhet här: https://gallery.technet.microsoft.com/Offline-Patch-Windows-f464594b

Jag har bantat ner scriptet för att bara hantera uppdateringar från en mapp där vi laddat hem alla .cab- och .msu-filer med hjälp av WSUS Offline Update – http://download.wsusoffline.net, ett suveränt verktyg för att ladda hem uppdateringar för respektive version av Windows.

Scriptet finns här:

 

<#

Patching VHD, VHDX or WIM files with Windows Update files (cab, msu).
Download all patches using WSUS Offline Update - http://download.wsusoffline.net/

Usage:
.Script.ps1 -PatchFolder D:Updates -ImagePath D:Template.vhdx

Ensure that you have a directory named "mount" in the same directory that you run the script from.

#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1,ParameterSetName="PatchFromLocalFolder")]
[ValidateScript({test-path $_})]
[string]$PatchFolder,

[Parameter(Mandatory=$True)]
[validatescript({test-path $_})]
[alias("VHDPath","WIMPath")]
[string]$ImagePath,

[validaterange(1,30)]
[int]
$imageIndex=1,

[Parameter(Mandatory=$false,position=2)]
[string]
[validatescript(
{
if(Test-Path $_){
$true
}else{
if(new-item -ItemType directory -Path $_ -ErrorAction SilentlyContinue){
$true
}else{
$false
}
}
}
)]

$MountDir=$(join-path (split-path $MyInvocation.MyCommand.path) "mount")
)
function
Get-ImageInfo {
param(
[parameter(mandatory=$true,position=0)]
[ValidateNotNullOrEmpty()]
[ValidateScript({test-path $(resolve-path $_)})]
[string]$mountDir
)
$offlineRegPath=join-path $mountdir "WindowsSystem32configsystem"
$offlineRegPath2=join-path $mountdir "WindowsSystem32configSOFTWARE"
if((Test-Path $offlineRegPath) -and (test-path $offlineRegPath2)){
reg load "HKLMTemp" $offlineRegPath | Out-Null
reg load "HKLMTemp2" $offlineRegPath2 | Out-null
if((Test-Path "HKLM:temp") -and  (Test-Path "HKLM:temp2")){
$arc=(Get-ItemProperty "HKLM:tempControlSet001ControlSession ManagerEnvironment").PROCESSOR_ARCHITECTURE
$osver=(Get-ItemProperty "HKLM:temp2MicrosoftWindows NTCurrentVersion").CurrentVersion
$installationType=(Get-ItemProperty "HKLM:temp2MicrosoftWindows NTCurrentVersion").InstallationType

$returnObj=New-Object psobject
$returnObj | Add-Member -MemberType NoteProperty -Name arch -Value $arc
$returnObj | Add-Member -MemberType NoteProperty -Name osVersion -Value $osver
if($installationType -eq 'server'){
$returnObj | Add-Member -MemberType NoteProperty -Name ComputerRole -Value $installationType
}

if($installationType -eq 'client'){
$returnObj | Add-Member -MemberType NoteProperty -Name ComputerRole -Value "workstation"
}

}
reg unload "HKLMTemp" | Out-Null
reg unload "HKLMTemp2" | Out-Null
return $returnObj
}
}
$scriptFolder=split-path $MyInvocation.MyCommand.path
$parameterSetName=$PsCmdlet.ParameterSetName

function
Write-Info {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
[ValidateNotNullOrEmpty()]
$text
)
Write-host "INFO : $($text)" -ForegroundColor green
}
function
Write-ToFile{
param(
[parameter(mandatory=$true)]
[string]
$message
)

$logPath="$scriptFolderPatchImage$([datetime]::today.tostring('yyyy-MM-dd')).log"

Add-Content -value "$(get-date) $message" -Path $logPath
}
Write-ToFile "Script at $($MyInvocation.MyCommand.path) starting..."
$outputMessage=$PSVersionTable | Out-String
Write-ToFile "Powershell Version: $outputMessage"
$os = gwmi win32_operatingsystem
$outputMessage=$os.Caption + " | Build Number: " + $os.BuildNumber
Write-ToFile "OSVersion: $outputMessage"
Write-ToFile "OSArch: $($env:PROCESSOR_ARCHITECTURE)"
Write-ToFile "DISMHost Version: $((Get-Item C:Windowssystem32dismDismHost.exe).VersionInfo.fileversion)"
Write-ToFile "Creating Shell Application Object..."
Write-Info "Creating Shell Application Object..."
$comObject = "Shell.Application"

$shell = New-Object -ComObject $comObject
if(!$?) { $(Throw "unable to create $comObject object")}
function
get-offlineImageinfo{
param(
[parameter(mandatory=$true,position=0)]
[ValidateNotNullOrEmpty()]
[ValidateScript({test-path $(resolve-path $_)})]
[string]$mountDir
)
$offlineRegPath=join-path $mountdir "WindowsSystem32configsystem"
$offlineRegPath2=join-path $mountdir "WindowsSystem32configSOFTWARE"
if((Test-Path $offlineRegPath) -and (test-path $offlineRegPath2)){
reg load "HKLMTemp" $offlineRegPath | Out-Null
reg load "HKLMTemp2" $offlineRegPath2 | Out-null
if((Test-Path "HKLM:temp") -and  (Test-Path "HKLM:temp2")){
$arc=(Get-ItemProperty "HKLM:tempControlSet001ControlSession ManagerEnvironment").PROCESSOR_ARCHITECTURE
$osver=(Get-ItemProperty "HKLM:temp2MicrosoftWindows NTCurrentVersion").CurrentVersion
$installationType=(Get-ItemProperty "HKLM:temp2MicrosoftWindows NTCurrentVersion").InstallationType

$returnObj = New-Object psobject
$returnObj | Add-Member -MemberType NoteProperty -Name arch -Value $arc
$returnObj | Add-Member -MemberType NoteProperty -Name osVersion -Value $osver
if($installationType -eq 'server'){
$returnObj | Add-Member -MemberType NoteProperty -Name ComputerRole -Value $installationType
}

if($installationType -eq 'client'){
$returnObj | Add-Member -MemberType NoteProperty -Name ComputerRole -Value "workstation"
}

}
reg unload "HKLMTemp" | Out-Null
reg unload "HKLMTemp2" | Out-Null
return $returnObj
}
}
function IsValidUpdateCab{
param(
[parameter(mandatory=$true)]
[string] $capFilePath
)

if(test-path $capFilePath){
$tempResult=$false
$items=$shell.Namespace($capFilePath).items()
foreach ($item in $items){
if($item.name -eq 'update.cat'){
$tempResult=$True
break
}
}

if($tempResult){
return $True
}else{
return $false
}
}else{
return $false
}
}
Write-ToFile "Mounting Windows Image file $ImagePath..."
Write-Info "Mounting Windows Image file $ImagePath..."

$mountStatus = Get-WindowsImage -Mounted
Write-ToFile "Check Image Mount Status"

if ($mountStatus -and ($mountstatus.imagepath -contains $imagePath)) {
Write-ToFile "Mount status: $($mountStatus | Out-String)"
Write-Info "Mount status: $($mountStatus | Out-String)"
Write-Warning "Image $imagepath is already mounted at $mountdir. Continuing in 5 seconds..."
Start-Sleep 5

} else {
Write-ToFile "Mouting Image $imagepath, Index $imageIndex, MountDir $mountdir"
Write-Info "Mouting Image $imagepath, Index $imageIndex, MountDir $mountdir"
Mount-WindowsImage -ImagePath $imagepath -Index $imageIndex -Path $MountDir | Out-Null
sleep -Seconds 5
$mountStatus=Get-WindowsImage -Mounted
if (!($mountStatus -and ($mountstatus.imagepath -contains $imagePath))) {
Write-Error "Error mounting Image $imagepath to directory $mountdir. Aborting."
Write-ToFile "Error mounting Image $imagepath to directory $mountdir. Aborting."
Return
}else{
Write-Info "Windows Image $ImagePath, ImageIndex=$imageIndex mounted at $MountDir successfull"
}
}
$Installables= @{}
$nonValidCabs= @{}

Write-ToFile "Get offline image infomation (image Mounted at $mountdir)"

$offlineImageInfo = get-offlineImageinfo -mountDir $MountDir
Write-Info "Image Information:"
$offlineImageInfo
Write-ToFile "Offline image Arch: $($offlineImageInfo.arch), OS-Version: $($offlineImageInfo.osVersion), ComputerRole: $($offlineImageInfo.computerRole)"
if($parameterSetName -eq "PatchFromLocalFolder"){

$allcabFiles = Get-ChildItem -Path $PatchFolder -Include *.cab -Recurse
Write-ToFile "Patching using updates in folder: $PatchFolder, we got a total of $($allcabFiles.count) cabs"
Write-Info "Patching using updates in folder: $PatchFolder, we got a total of $($allcabFiles.count) cabs"
$successcount=0
foreach($cabfile in $allcabFiles){
if(IsValidUpdateCab $cabfile){
try{
Add-WindowsPackage -PackagePath $cabfile.FullName -Path $MountDir -ErrorAction SilentlyContinue
Write-ToFile " Installing $($cabfile.FullName) successfull"
Write-Info "Installing $($cabfile.FullName) successfull"
$successcount++
}catch{
Write-ToFile " -> Error! Patching $($cabfile.FullName) failed <-"
Write-Info "-> Error! Patching $($cabfile.FullName) failed <-"

}
}else{
Write-ToFile "Cab file $($cabfile.FullName) is not an valid update cab file"
Write-Info "Cab file $($cabfile.FullName) is not an valid update cab file"
}
}

Write-ToFile "Patching cab files result: Total=$($allcabFiles.count), Success=$successcount, Failed=$($allcabFiles.count-$successcount)"
Write-Info "Patching cab files result: Total=$($allcabFiles.count), Success=$successcount, Failed=$($allcabFiles.count-$successcount)"
Write-Info "Script are now looking for MSU-files in $PatchFolder to be installed..."

$allmsuFiles=Get-ChildItem -Path $PatchFolder -Include *.msu -Recurse
Write-ToFile "Patching using updates in folder: $PatchFolder, we got a total of $($allmsuFiles.count) MSU-files"
Write-Info "Patching using updates in folder: $PatchFolder, we got a total of $($allmsuFiles.count) MSU-files"
$successcount=0
foreach($msufile in $allmsuFiles){
try{
Add-WindowsPackage -PackagePath $msufile.FullName -Path $MountDir -ErrorAction SilentlyContinue
Write-ToFile " Installed $($msufile.FullName) successfull"
Write-Info "Installed $($msufile.FullName) successfull"
$successcount++
}catch{
Write-ToFile " -> Patching $($msufile.FullName) failed"
Write-Info " -> Patching $($msufile.FullName) failed"
}

}

Write-ToFile "Patching MSU-files result: Total=$($allmsuFiles.count), Success=$successcount, Failed=$($allmsuFiles.count-$successcount)"
}

if (-Not $WhatIf) {
Write-ToFile "Commiting changes to image file ..."
Write-Info "Commiting changes to image file ..."
Dismount-WindowsImage -Path $MountDir -Save | Out-Null
} else {
Write-ToFile "Discarding changes to image file ..."
Write-Info "Discarding changes to image file ..."
Dismount-WindowsImage -Path $MountDir -Discard | Out-Null
}

$shell=$null
Write-ToFile "Finished."
Write-Info "Patching done!"

 

Happy Patching!Patching script

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: