Public/Functions/Dism.ps1
<#
.SYNOPSIS Adds the SSU from a Cumulative Update .cab or .msu to a Windows Image .DESCRIPTION The Add-WindowsPackageSSU cmdlet installs a specified .cab or .msu package in the image .PARAMETER Path Specifies the full path to the root directory of the offline Windows image that you will service. .PARAMETER PackagePath Specifies the location of the package to add to the image .PARAMETER Online Specifies that the action is to be taken on the operating system that is currently running on the local computer. .PARAMETER LogPath Specifies the full path and file name to log to. If not set, the default is %WINDIR%\Logs\Dism\dism.log. In Windows PE, the default directory is the RAMDISK scratch space which can be as low as 32 MB. The log file will automatically be archived. The archived log file will be saved with .bak appended to the file name and a new log file will be generated. Each time the log file is archived the .bak file will be overwritten. When using a network share that is not joined to a domain, use the net use command together with domain credentials to set access permissions before you set the log path for the DISM log. .LINK .NOTES #> function Add-WindowsPackageSSU { [CmdletBinding(DefaultParameterSetName = 'Offline')] param ( [Parameter(Mandatory = $true)] [string]$PackagePath, [Parameter(ParameterSetName = 'Offline', Mandatory = $true)] [string]$Path, [Parameter(ParameterSetName = 'Online', Mandatory = $true)] [System.Management.Automation.SwitchParameter]$Online, [string]$LogPath = "$env:windir\Logs\Dism\dism.log" ) #================================================= # Blocks #================================================= Block-StandardUser Block-WindowsVersionNe10 #================================================= # Test PackagePath #================================================= if (!(Test-Path "$PackagePath" -PathType Leaf)) { Write-Warning "Add-WindowsPackageLCU could not find $Path"; Continue } $PackagePathItem = Get-Item $PackagePath #================================================= # SSU Temp Path #================================================= $SSUTemp = Join-Path $env:Temp 'SSU' #See if the path already exists and remove it if (Test-Path $SSUTemp) { Remove-Item -Path $SSUTemp -Recurse -Force -ErrorAction Ignore | Out-Null } #Create the SSU Temp Path New-Item -Path $SSUTemp -ItemType Directory -Force -ErrorAction Ignore | Out-Null #Bail if SSU Temp Path doesn't exist if (!(Test-Path $SSUTemp)) { Write-Warning "Add-WindowsPackageLCU could not create $SSUTemp"; Continue } #================================================= # Expand MSU #================================================= if ($PackagePathItem.Extension -match '.msu') { & Expand.exe "$($PackagePathItem.FullName)" /f:Windows*.cab "$SSUTemp" Get-ChildItem -Path $SSUTemp *.cab | Where-Object {$_.Name -notmatch 'SSU'} | foreach { & Expand.exe $_.FullName /f:SSU*.cab "$SSUTemp" } } else { #Write-Host -ForegroundColor DarkGray "Expand SSU: $PackagePath" & Expand.exe "$($PackagePathItem.FullName)" /f:SSU*.cab "$SSUTemp" } #================================================= # Apply SSU #================================================= if ($Online.IsPresent) { Get-ChildItem -Path $SSUTemp SSU*.cab | foreach { Write-Host -ForegroundColor DarkGray $_.FullName Add-WindowsPackage -PackagePath $_.FullName -Online -LogPath $LogPath -Verbose | Out-Null } } else { Get-ChildItem -Path $SSUTemp SSU*.cab | foreach { Write-Host -ForegroundColor DarkGray $_.FullName Add-WindowsPackage -PackagePath $_.FullName -Path $Path -LogPath $LogPath -Verbose | Out-Null } } #================================================= } function Copy-PSModuleToWindowsImage { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [SupportsWildcards()] [String[]]$Name, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateSet('Restricted','AllSigned','RemoteSigned','Unrestricted','Bypass','Undefined')] [string]$ExecutionPolicy, [Parameter(ValueFromPipelineByPropertyName = $true)] [string[]]$Path ) begin { #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Get-WindowsImage Mounted #================================================= if ($null -eq $Path) { $Path = (Get-WindowsImage -Mounted | Select-Object -Property Path).Path } #================================================= } process { foreach ($Input in $Path) { #================================================= # Path #================================================= $MountPath = (Get-Item -Path $Input | Select-Object FullName).FullName Write-Verbose "Path: $MountPath" #================================================= # Validate Mount Path #================================================= if (-not (Test-Path $Input -ErrorAction SilentlyContinue)) { Write-Warning "Unable to locate Mounted WindowsImage at $Input" Break } #================================================= # Copy-PSoduleToFolder #================================================= Copy-PSModuleToFolder -Name $Name -Destination "$MountPath\Program Files\WindowsPowerShell\Modules" -RemoveOldVersions #================================================= # Set-WindowsImageExecutionPolicy #================================================= if ($ExecutionPolicy) { Set-WindowsImageExecutionPolicy -ExecutionPolicy $ExecutionPolicy -Path $MountPath } #================================================= # Return for PassThru #================================================= Return Get-WindowsImage -Mounted | Where-Object {$_.Path -eq $MountPath} #================================================= } } end {} } <# .SYNOPSIS Dismounts a Windows image from the directory it is mapped to. .DESCRIPTION The Dismount-WindowsImage cmdlet either saves or discards the changes to a Windows image and then dismounts the image. .PARAMETER Path Specifies the full path to the root directory of the offline Windows image that you will service. .PARAMETER Discard Discards the changes to a Windows image. .PARAMETER Save Saves the changes to a Windows image. .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .INPUTS System.String[] .INPUTS Microsoft.Dism.Commands.ImageObject .INPUTS Microsoft.Dism.Commands.MountedImageInfoObject .INPUTS Microsoft.Dism.Commands.ImageInfoObject .OUTPUTS Microsoft.Dism.Commands.BaseDismObject .NOTES 19.11.21 Initial Release 21.2.9 Renamed from Dismount-WindowsImageOSD #> function Dismount-MyWindowsImage { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'DismountDiscard')] param ( [Parameter(ValueFromPipelineByPropertyName)] [string[]]$Path, [Parameter(ParameterSetName = 'DismountDiscard', Mandatory = $true)] [System.Management.Automation.SwitchParameter]$Discard, [Parameter(ParameterSetName = 'DismountSave', Mandatory = $true)] [System.Management.Automation.SwitchParameter]$Save ) begin { #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Get-WindowsImage Mounted #================================================= if ($null -eq $Path) { $Path = (Get-WindowsImage -Mounted | Select-Object -Property Path).Path } #================================================= } process { foreach ($Input in $Path) { #================================================= # Path #================================================= $MountPath = (Get-Item -Path $Input | Select-Object FullName).FullName Write-Verbose "Path: $MountPath" #================================================= # Validate Mount Path #================================================= if (-not (Test-Path $Input -ErrorAction SilentlyContinue)) { Write-Warning "Dismount-MyWindowsImage: Unable to locate Mounted WindowsImage at $Input" Break } #================================================= # Dismount-WindowsImage #================================================= if ($Discard.IsPresent) { if ($PSCmdlet.ShouldProcess($Input, "Dismount-MyWindowsImage -Discard")) { Dismount-WindowsImage -Path $Input -Discard | Out-Null } } if ($Save.IsPresent) { if ($PSCmdlet.ShouldProcess($Input, "Dismount-MyWindowsImage -Save")) { Dismount-WindowsImage -Path $Input -Save | Out-Null } } } } end {} } <# .SYNOPSIS Edits a mounted Windows Image .DESCRIPTION Edits a mounted Windows Image .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES 19.11.22 David Segura @SeguraOSD #> function Edit-MyWindowsImage { [CmdletBinding(DefaultParameterSetName = 'Offline')] param ( #Specifies the full path to the root directory of the offline Windows image that you will service. #If the directory named Windows is not a subdirectory of the root directory, -WindowsDirectory must be specified. [Parameter(ParameterSetName = 'Offline', ValueFromPipelineByPropertyName)] [string[]]$Path, #Dism Actions #Analyze cannot be used for PassThru [Parameter(ParameterSetName = 'Offline')] [ValidateSet('Analyze','Cleanup','CleanupResetBase')] [string]$CleanupImage, #Specifies that the action is to be taken on the operating system that is currently running on the local computer. [Parameter(ParameterSetName = 'Online', Mandatory = $true)] [System.Management.Automation.SwitchParameter]$Online, #Appx Packages selected in GridView will be removed from the Windows Image [Parameter(ParameterSetName = 'Online')] [System.Management.Automation.SwitchParameter]$GridRemoveAppx, #Appx Provisioned Packages selected in GridView will be removed from the Windows Image [System.Management.Automation.SwitchParameter]$GridRemoveAppxPP, #Appx Packages matching the string will be removed [Parameter(ParameterSetName = 'Online')] [string[]]$RemoveAppx, #Appx Provisioned Packages matching the string will be removed [string[]]$RemoveAppxPP, [System.Management.Automation.SwitchParameter]$DismountSave ) begin { #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Get Registry Information #================================================= $GetRegCurrentVersion = Get-RegCurrentVersion #================================================= # Require OSMajorVersion 10 #================================================= if ($GetRegCurrentVersion.CurrentMajorVersionNumber -ne 10) { Write-Warning "$($MyInvocation.MyCommand) requires OS MajorVersion 10" Break } #================================================= } process { if ($PSCmdlet.ParameterSetName -eq 'Online') { #================================================= # Get Registry Information #================================================= $GetRegCurrentVersion = Get-RegCurrentVersion #================================================= # Require OSMajorVersion 10 #================================================= if ($GetRegCurrentVersion.CurrentMajorVersionNumber -ne 10) { Write-Warning "Edit-MyWindowsImage: OS MajorVersion 10 is required" Break } #================================================= # GridRemoveAppx #================================================= if ($GridRemoveAppx.IsPresent) { Get-AppxPackage | Select-Object * | Where-Object {$_.NonRemovable -ne $true} | Out-GridView -PassThru -Title "Select Appx Packages to Remove from Online Windows Image" | ForEach-Object { Remove-AppPackage -AllUsers -Package $_.PackageFullName -Verbose } } #================================================= # GridRemoveAppxPP #================================================= if ($GridRemoveAppxPP.IsPresent) { Get-AppxProvisionedPackage -Online | Select-Object DisplayName, PackageName | Out-GridView -PassThru -Title "Select Appx Provisioned Packages to Remove from Online Windows Image" | Remove-AppProvisionedPackage -Online -AllUsers } #================================================= # RemoveAppx #================================================= if ($RemoveAppx) { foreach ($Item in $RemoveAppx) { Get-AppxPackage | Where-Object {$_.Name -Match $Item} | ForEach-Object { Write-Verbose "$($_.Name): Removing Appx Package $($_.PackageFullName)" -Verbose Try {Remove-AppxPackage -AllUsers -Package $_.PackageFullName | Out-Null} Catch {Write-Warning "$($_.Name): Removing Appx Package $($_.PackageFullName) did not complete successfully"} } } } #================================================= # RemoveAppxPP #================================================= if ($RemoveAppxPP) { foreach ($Item in $RemoveAppxPP) { Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -Match $Item} | ForEach-Object { Write-Verbose "$($_.DisplayName): Removing Appx Provisioned Package $($_.PackageName)" -Verbose Try {Remove-AppxProvisionedPackage -Online -AllUsers -PackageName $_.PackageName | Out-Null} Catch {Write-Warning "$($_.DisplayName): Removing Appx Provisioned Package $($_.PackageName) did not complete successfully"} } } } #================================================= # Continue for PassThru #================================================= Continue } if ($PSCmdlet.ParameterSetName -eq 'Offline') { #================================================= # Get-WindowsImage Mounted #================================================= if ($null -eq $Path) { $Path = (Get-WindowsImage -Mounted | Select-Object -Property Path).Path } foreach ($Input in $Path) { Write-Verbose "Edit-MyWindowsImage $Input" #================================================= # Get-Item #================================================= if (Get-Item $Input -ErrorAction SilentlyContinue) { $GetItemInput = Get-Item -Path $Input } else { Write-Warning "Unable to locate WindowsImage at $Input" Continue } #================================================= # Mount-MyWindowsImage #================================================= try { $MountMyWindowsImage = Mount-MyWindowsImage -ImagePath $Input -Index $Index } catch { Write-Warning "Could not mount this WIM for some reason" Continue } if ($null -eq $MountMyWindowsImage) { Write-Warning "Could not mount this WIM for some reason" Continue } #================================================= # Make sure WinPE is Major Version 10 #================================================= Write-Verbose "Verifying WinPE 10" $GetRegCurrentVersion = Get-RegCurrentVersion -Path $MountMyWindowsImage.Path if ($GetRegCurrentVersion.CurrentMajorVersionNumber -ne 10) { Write-Warning "$($MyInvocation.MyCommand) can only service WinPE with MajorVersion 10" $MountMyWindowsImage | Dismount-MyWindowsImage -Discard Continue } #================================================= # GridRemoveAppxPP #================================================= if ($GridRemoveAppxPP.IsPresent) { $CurrentLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Edit-MyWindowsImage.log" Get-AppxProvisionedPackage -Path $Input | Select-Object DisplayName, PackageName | Out-GridView -PassThru -Title "Select Appx Provisioned Packages to Remove from $Input" | Remove-AppProvisionedPackage -Path $Input -LogPath $CurrentLog } #================================================= # RemoveAppxPP #================================================= if ($RemoveAppxPP) { foreach ($Item in $RemoveAppxPP) { Write-Verbose "RemoveAppxPP: $Item" Get-AppxProvisionedPackage -Path $Input | Where-Object {$_.DisplayName -Match $Item} | ForEach-Object { $DismLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Edit-MyWindowsImage.log" Write-Verbose "$($_.DisplayName): Removing Appx Provisioned Package $($_.PackageName)" -Verbose Remove-AppxProvisionedPackage -Path $_.Path -PackageName $_.PackageName -LogPath $DismLog | Out-Null } } } #================================================= # Cleanup #================================================= if ($CleanupImage -eq 'Analyze') { $DismLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-Analyze-Dism.log" $ConsoleLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-Analyze-Console.log" Write-Verbose "DISM /Image:$Input /Cleanup-Image /AnalyzeComponentStore" -Verbose Write-Warning "Console Output is being redirected to $ConsoleLog" DISM /Image:"$Input" /Cleanup-Image /AnalyzeComponentStore /LogPath:"$DismLog" *> $ConsoleLog } if ($CleanupImage -eq 'Cleanup') { $DismLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-Cleanup-Dism.log" $ConsoleLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-Cleanup-Console.log" Write-Verbose "DISM /Image:$Input /Cleanup-Image /StartComponentCleanup" -Verbose Write-Warning "This process will take between 1 - 200 minutes to complete, depending on the number of Updates" Write-Warning "Console Output is being redirected to $ConsoleLog" DISM /Image:"$Input" /Cleanup-Image /StartComponentCleanup /LogPath:"$DismLog" *> $ConsoleLog } if ($CleanupImage -eq 'CleanupResetBase') { $DismLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-CleanupResetBase-Dism.log" $ConsoleLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Cleanup-Image-CleanupResetBase-Console.log" Write-Verbose "DISM /Image:$Input /Cleanup-Image /StartComponentCleanup /ResetBase" -Verbose Write-Warning "This process will take between 1 - 200 minutes to complete, depending on the number of Updates" Write-Warning "Console Output is being redirected to $ConsoleLog" DISM /Image:"$Input" /Cleanup-Image /StartComponentCleanup /ResetBase /LogPath:"$DismLog" *> $ConsoleLog } #================================================= # Dismount-MyWindowsImage #================================================= if ($DismountSave) { $MountMyWindowsImage | Dismount-MyWindowsImage -Save } else { $MountMyWindowsImage } #================================================= } } } end {} } function Get-MyWindowsCapability { [CmdletBinding(DefaultParameterSetName = 'Online')] param ( [Parameter(Mandatory = $true, ParameterSetName = "Offline", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)] [string]$Path, [ValidateSet('Installed','NotPresent')] [string]$State, [ValidateSet('Language','Rsat','Other')] [string]$Category, [string[]]$Culture, [string[]]$Like, [string[]]$Match, [System.Management.Automation.SwitchParameter]$Detail, [Parameter(ParameterSetName = "Online")] [System.Management.Automation.SwitchParameter]$DisableWSUS ) begin { #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Test Get-WindowsCapability #================================================= if (Get-Command -Name Get-WindowsCapability -ErrorAction SilentlyContinue) { Write-Verbose 'Verified command Get-WindowsCapability' } else { Write-Warning 'Get-MyWindowsCapability requires Get-WindowsCapability which is not present' Break } #================================================= # Verify BuildNumber #================================================= $MinimumBuildNumber = 17763 $CurrentBuildNumber = (Get-CimInstance -Class Win32_OperatingSystem).BuildNumber if ($MinimumBuildNumber -gt $CurrentBuildNumber) { Write-Warning "The current Windows BuildNumber is $CurrentBuildNumber" Write-Warning "Get-MyWindowsCapability requires Windows BuildNumber greater than $MinimumBuildNumber" Break } #================================================= # UseWUServer # Original code from Martin Bengtsson # https://www.imab.dk/deploy-rsat-remote-server-administration-tools-for-windows-10-v2004-using-configmgr-and-powershell/ # https://github.com/imabdk/Powershell/blob/master/Install-RSATv1809v1903v1909v2004v20H2.ps1 #================================================= $WUServer = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUServer -ErrorAction Ignore).WUServer $UseWUServer = (Get-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" -ErrorAction Ignore).UseWuServer if ($PSCmdlet.ParameterSetName -eq 'Online') { if (($WUServer -ne $null) -and ($UseWUServer -eq 1) -and ($DisableWSUS -eq $false)) { Write-Warning "This computer is configured to receive updates from WSUS Server $WUServer" Write-Warning "Piping to Add-WindowsCapability may not function properly" Write-Warning "Local Source: Get-MyWindowsCapability | Add-WindowsCapability -Source" Write-Warning "Windows Update: Get-MyWindowsCapability -DisableWSUS | Add-WindowsCapability" } if (($DisableWSUS -eq $true) -and ($UseWUServer -eq 1)) { Set-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "UseWuServer" -Value 0 Restart-Service wuauserv } } #================================================= # Get Module Path #================================================= $GetModuleBase = Get-Module -Name OSD | Select-Object -ExpandProperty ModuleBase -First 1 #================================================= } process { #================================================= # Get-WindowsCapability #================================================= if ($PSCmdlet.ParameterSetName -eq 'Online') { $GetAllItems = Get-WindowsCapability -Online } if ($PSCmdlet.ParameterSetName -eq 'Offline') { $GetAllItems = Get-WindowsCapability -Path $Path } #================================================= # Like #================================================= foreach ($Item in $Like) { $GetAllItems = $GetAllItems | Where-Object {$_.Name -like "$Item"} } #================================================= # Match #================================================= foreach ($Item in $Match) { $GetAllItems = $GetAllItems | Where-Object {$_.Name -match "$Item"} } #================================================= # State #================================================= if ($State) {$GetAllItems = $GetAllItems | Where-Object {$_.State -eq $State}} #================================================= # Category #================================================= if ($Category -eq 'Other') { $GetAllItems = $GetAllItems | Where-Object {$_.Name -notmatch 'Language'} $GetAllItems = $GetAllItems | Where-Object {$_.Name -notmatch 'Rsat'} } if ($Category -eq 'Language') { $GetAllItems = $GetAllItems | Where-Object {$_.Name -match 'Language'} } if ($Category -eq 'Rsat') { $GetAllItems = $GetAllItems | Where-Object {$_.Name -match 'Rsat'} } #================================================= # Culture #================================================= $FilteredItems = @() if ($Culture) { foreach ($Item in $Culture) { $FilteredItems += $GetAllItems | Where-Object {$_.Name -match $Item} } } else { $FilteredItems = $GetAllItems } #================================================= # Dictionary #================================================= if (Test-Path "$GetModuleBase\Resources\Dictionary\Get-MyWindowsCapability.json") { $GetAllItemsDictionary = Get-Content "$GetModuleBase\Resources\Dictionary\Get-MyWindowsCapability.json" | ConvertFrom-Json } #================================================= # Create Object #================================================= if ($Detail -eq $true) { $Results = foreach ($Item in $FilteredItems) { $ItemProductName = ($Item.Name -split ',*~')[0] $ItemCulture = ($Item.Name -split ',*~')[3] $ItemVersion = ($Item.Name -split ',*~')[4] $ItemDetails = $null $ItemDetails = $GetAllItemsDictionary | ` Where-Object {($_.ProductName -eq $ItemProductName)} | ` Where-Object {($_.Culture -eq $ItemCulture)} | ` Select-Object -First 1 if ($null -eq $ItemDetails) { Write-Verbose "$($Item.Name) ... gathering details" -Verbose if ($PSCmdlet.ParameterSetName -eq 'Online') { $ItemDetails = Get-WindowsCapability -Name $Item.Name -Online } if ($PSCmdlet.ParameterSetName -eq 'Offline') { $ItemDetails = Get-WindowsCapability -Name $Item.Name -Path $Path } } if ($PSCmdlet.ParameterSetName -eq 'Online') { [PSCustomObject] @{ DisplayName = $ItemDetails.DisplayName Culture = $ItemCulture Version = $ItemVersion State = $Item.State Description = $ItemDetails.Description Name = $Item.Name Online = $Item.Online ProductName = $ItemProductName } } if ($PSCmdlet.ParameterSetName -eq 'Offline') { [PSCustomObject] @{ DisplayName = $ItemDetails.DisplayName Culture = $ItemCulture Version = $ItemVersion State = $Item.State Description = $ItemDetails.Description Name = $Item.Name Path = $Item.Path ProductName = $ItemProductName } } } } else { $Results = foreach ($Item in $FilteredItems) { $ItemProductName = ($Item.Name -split ',*~')[0] $ItemCulture = ($Item.Name -split ',*~')[3] $ItemVersion = ($Item.Name -split ',*~')[4] if ($PSCmdlet.ParameterSetName -eq 'Online') { [PSCustomObject] @{ ProductName = $ItemProductName Culture = $ItemCulture Version = $ItemVersion State = $Item.State Name = $Item.Name Online = $Item.Online } } if ($PSCmdlet.ParameterSetName -eq 'Offline') { [PSCustomObject] @{ ProductName = $ItemProductName Culture = $ItemCulture Version = $ItemVersion State = $Item.State Name = $Item.Name Path = $Item.Path } } } } #================================================= # Rebuild Dictionary #================================================= $Results | ` Sort-Object ProductName, Culture | ` Select-Object Name, ProductName, Culture, DisplayName, Description | ` ConvertTo-Json | ` Out-File "$env:TEMP\Get-MyWindowsCapability.json" -Width 2000 -Force #================================================= # Install / Return #================================================= if ($Install -eq $true) { foreach ($Item in $Results) { if ($_.State -eq 'Installed') { Write-Verbose "$_.Name is already installed" -Verbose } else { $Item | Add-WindowsCapability -Online } } } else { Return $Results } #================================================= } end { if (($DisableWSUS -eq $true) -and ($UseWUServer -eq 1)) { Set-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "UseWuServer" -Value $UseWUServer Restart-Service wuauserv } } } function Get-MyWindowsPackage { [CmdletBinding(DefaultParameterSetName = 'Online')] param ( [Parameter(Mandatory = $true, ParameterSetName = "Offline", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)] [string]$Path, [ValidateSet('Installed','Superseded')] [string]$PackageState, [ValidateSet('FeaturePack','Foundation','LanguagePack','OnDemandPack','SecurityUpdate','Update')] [string]$ReleaseType, [ValidateSet('FOD','Language','LanguagePack','Update','Other')] [string]$Category, [string[]]$Culture, [string[]]$Like, [string[]]$Match, [System.Management.Automation.SwitchParameter]$Detail ) #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Test Get-WindowsPackage #================================================= if (Get-Command -Name Get-WindowsPackage -ErrorAction SilentlyContinue) { Write-Verbose 'Verified command Get-WindowsPackage' } else { Write-Warning 'Get-MyWindowsPackage requires Get-WindowsPackage which is not present' Break } #================================================= # Get Module Path #================================================= $GetModuleBase = Get-Module -Name OSD | Select-Object -ExpandProperty ModuleBase -First 1 #================================================= # Get-WindowsPackage #================================================= if ($PSCmdlet.ParameterSetName -eq 'Online') { $GetAllItems = Get-WindowsPackage -Online } if ($PSCmdlet.ParameterSetName -eq 'Offline') { $GetAllItems = Get-WindowsPackage -Path $Path } #================================================= # Like #================================================= foreach ($Item in $Like) { $GetAllItems = $GetAllItems | Where-Object {$_.PackageName -like "$Item"} } #================================================= # Match #================================================= foreach ($Item in $Match) { $GetAllItems = $GetAllItems | Where-Object {$_.PackageName -match "$Item"} } #================================================= # PackageState #================================================= if ($PackageState) {$GetAllItems = $GetAllItems | Where-Object {$_.PackageState -eq $PackageState}} #================================================= # ReleaseType #================================================= if ($ReleaseType) {$GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -eq $ReleaseType}} #================================================= # Category #================================================= #Get-MyWindowsPackage -Category FOD if ($Category -eq 'FOD') { $GetAllItems = $GetAllItems | Where-Object {$_.PackageName -match 'FOD'} } #Get-MyWindowsPackage -Category Language if ($Category -eq 'Language') { $GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -ne 'LanguagePack'} $GetAllItems = $GetAllItems | Where-Object {($_.PackageName -split ',*~')[3] -ne ''} } #Get-MyWindowsPackage -Category LanguagePack if ($Category -eq 'LanguagePack') { $GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -eq 'LanguagePack'} } #Get-MyWindowsPackage -Category Update if ($Category -eq 'Update') { $GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -match 'Update'} } #Get-MyWindowsPackage -Category Other if ($Category -eq 'Other') { $GetAllItems = $GetAllItems | Where-Object {$_.PackageName -notmatch 'FOD'} $GetAllItems = $GetAllItems | Where-Object {($_.PackageName -split ',*~')[3] -eq ''} $GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -ne 'LanguagePack'} $GetAllItems = $GetAllItems | Where-Object {$_.ReleaseType -notmatch 'Update'} } #================================================= # Culture #================================================= $FilteredItems = @() if ($Culture) { foreach ($Item in $Culture) { $FilteredItems += $GetAllItems | Where-Object {$_.PackageName -match "$Item"} } } else { $FilteredItems = $GetAllItems } #================================================= # Dictionary #================================================= if (Test-Path "$GetModuleBase\Resources\Dictionary\Get-MyWindowsPackage.json") { $GetAllItemsDictionary = Get-Content "$GetModuleBase\Resources\Dictionary\Get-MyWindowsPackage.json" | ConvertFrom-Json } #================================================= # Create Object #================================================= if ($Detail -eq $true) { $Results = foreach ($Item in $FilteredItems) { $ItemProductName = ($Item.PackageName -split ',*~')[0] $ItemArchitecture = ($Item.PackageName -split ',*~')[2] $ItemCulture = ($Item.PackageName -split ',*~')[3] $ItemVersion = ($Item.PackageName -split ',*~')[4] $ItemDetails = $null $ItemDetails = $GetAllItemsDictionary | ` Where-Object {($_.ProductName -notmatch 'Package_for_DotNetRollup')} | ` Where-Object {($_.ProductName -notmatch 'Package_for_RollupFix')} | ` Where-Object {($_.ProductName -eq $ItemProductName)} | ` Where-Object {($_.Culture -eq $ItemCulture)} | ` Select-Object -First 1 if ($null -eq $ItemDetails) { Write-Verbose "$($Item.PackageName) ... gathering details" -Verbose if ($PSCmdlet.ParameterSetName -eq 'Online') { $ItemDetails = Get-WindowsPackage -PackageName $Item.PackageName -Online } if ($PSCmdlet.ParameterSetName -eq 'Offline') { $ItemDetails = Get-WindowsPackage -PackageName $Item.PackageName -Path $Path } } $DisplayName = $ItemDetails.DisplayName if ($DisplayName -eq '') {$DisplayName = $ItemProductName} if ($ItemProductName -match 'Package_for_DotNetRollup') {$DisplayName = 'DotNet_Cumulative_Update'} if ($ItemProductName -match 'Package_for_RollupFix') {$DisplayName = 'Latest_Cumulative_Update'} if ($ItemProductName -match 'Package_for_KB') {$DisplayName = ("$ItemProductName" -replace "Package_for_")} if ($PSCmdlet.ParameterSetName -eq 'Online') { [PSCustomObject] @{ DisplayName = $DisplayName Architecture = $ItemArchitecture Culture = $ItemCulture Version = $ItemVersion ReleaseType = $Item.ReleaseType PackageState = $Item.PackageState InstallTime = $Item.InstallTime CapabilityId = $ItemDetails.CapabilityId Description = $ItemDetails.Description PackageName = $Item.PackageName Online = $Item.Online ProductName = $ItemProductName } } if ($PSCmdlet.ParameterSetName -eq 'Offline') { [PSCustomObject] @{ DisplayName = $DisplayName Architecture = $ItemArchitecture Culture = $ItemCulture Version = $ItemVersion ReleaseType = $Item.ReleaseType PackageState = $Item.PackageState InstallTime = $Item.InstallTime CapabilityId = $ItemDetails.CapabilityId Description = $ItemDetails.Description PackageName = $Item.PackageName Path = $Item.Path ProductName = $ItemProductName } } } } else { #Build Object $Results = foreach ($Item in $FilteredItems) { $ItemProductName = ($Item.PackageName -split ',*~')[0] $ItemArchitecture = ($Item.PackageName -split ',*~')[2] $ItemCulture = ($Item.PackageName -split ',*~')[3] $ItemVersion = ($Item.PackageName -split ',*~')[4] if ($PSCmdlet.ParameterSetName -eq 'Online') { [PSCustomObject] @{ ProductName = $ItemProductName Architecture = $ItemArchitecture Culture = $ItemCulture Version = $ItemVersion ReleaseType = $Item.ReleaseType PackageState = $Item.PackageState InstallTime = $Item.InstallTime PackageName = $Item.PackageName Online = $Item.Online } } if ($PSCmdlet.ParameterSetName -eq 'Offline') { [PSCustomObject] @{ ProductName = $ItemProductName Architecture = $ItemArchitecture Culture = $ItemCulture Version = $ItemVersion ReleaseType = $Item.ReleaseType PackageState = $Item.PackageState InstallTime = $Item.InstallTime PackageName = $Item.PackageName Path = $Item.Path } } } } #================================================= # Rebuild Dictionary #================================================= $Results | ` Sort-Object ProductName, Culture | ` Where-Object {$_.Architecture -notmatch 'wow64'} | ` Where-Object {$_.ProductName -notmatch 'Package_for_DotNetRollup'} | ` Where-Object {$_.ProductName -notmatch 'Package_for_RollupFix'} | ` Where-Object {$_.PackageState -ne 'Superseded'} | ` Select-Object PackageName, ProductName, Architecture, Culture, DisplayName, CapabilityId, Description | ` ConvertTo-Json | ` Out-File "$env:TEMP\Get-MyWindowsPackage.json" -Width 2000 -Force #================================================= # Return #================================================= Return $Results #================================================= } <# .SYNOPSIS Mounts a WIM file .DESCRIPTION Mounts a WIM file automatically selecting the Path and the Index .PARAMETER ImagePath Specifies the full path to the Windows Image .PARAMETER Index Index of the Windows Image .PARAMETER ReadOnly Mount the Windows Image as Read Only .PARAMETER Explorer Opens Windows Explorer to the Mount Directory .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES #> function Mount-MyWindowsImage { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName )] [string[]]$ImagePath, [Parameter( ValueFromPipelineByPropertyName )] [UInt32]$Index = 1, [Parameter( ValueFromPipelineByPropertyName )] [System.Management.Automation.SwitchParameter]$ReadOnly, [Parameter( ValueFromPipelineByPropertyName )] [System.Management.Automation.SwitchParameter]$Explorer ) begin { #================================================= # Require Admin Rights #================================================= if ((Get-OSDGather -Property IsAdmin) -eq $false) { Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED" Break } #================================================= # Get Registry Information #================================================= $GetRegCurrentVersion = Get-RegCurrentVersion #================================================= # Require OSMajorVersion 10 #================================================= if ($GetRegCurrentVersion.CurrentMajorVersionNumber -ne 10) { Write-Warning "$($MyInvocation.MyCommand) requires OS MajorVersion 10" Break } #================================================= } process { foreach ($Input in $ImagePath) { Write-Verbose "$Input" #================================================= # Get-Item #================================================= if (Get-Item $Input -ErrorAction SilentlyContinue) { $GetItemInput = Get-Item -Path $Input } else { Write-Warning "Unable to locate WindowsImage at $Input" Break } #================================================= # Directory #================================================= if ($GetItemInput.PSIsContainer) { Write-Verbose "Directory was not expected" if (Test-WindowsImageMountPath -Path $GetItemInput.FullName) { Write-Verbose "Windows Image is already mounted in this Directory" Write-Verbose "Returning Mount-WindowsImage Object" Get-WindowsImage -Mounted | Where-Object {($_.Path -eq $GetItemInput.FullName) -and ($_.ImageIndex -eq $Index)} Continue } else { Write-Warning "There isn't really anything that I can do with this directory. Goodbye!" Continue } } #================================================= # Read Only #================================================= if ($GetItemInput.IsReadOnly) { Write-Warning "Cannot Mount this Read Only Image. Goodbye!" Continue } #================================================= # Already Mounted #================================================= if (Test-WindowsImageMounted -ImagePath $GetItemInput.FullName -Index $Index) { Write-Verbose "Windows Image is already mounted" Write-Verbose "Returning Mount-WindowsImage Object" Get-WindowsImage -Mounted | Where-Object {($_.ImagePath -eq $GetItemInput.FullName) -and ($_.ImageIndex -eq $Index)} Continue } #================================================= # Not a Windows Image #================================================= if (-Not (Test-WindowsImage -ImagePath $GetItemInput.FullName)) { Write-Warning "Does not appear to be a Windows Image. Goodbye!" Continue } #================================================= # Set Mount Path #================================================= $MyWindowsImageMountPath = $env:Temp + '\Mount' + (Get-Random) if (-NOT (Test-Path $MyWindowsImageMountPath)) { New-Item $MyWindowsImageMountPath -ItemType Directory -Force -ErrorAction Stop | Out-Null } $Path = (Get-Item $MyWindowsImageMountPath).FullName #================================================= # Mount-WindowsImage #================================================= $Params = @{ Path = $Path ImagePath = $GetItemInput.FullName Index = $Index ReadOnly = $ReadOnly } Mount-WindowsImage @Params | Out-Null #================================================= # Explorer #================================================= if ($Explorer.IsPresent) {explorer $Path} #================================================= # Return for PassThru #================================================= Get-WindowsImage -Mounted | Where-Object {$_.Path -eq $Path} } } end {} } <# .SYNOPSIS Removes Appx Packages and Appx Provisioned Packages for All Users .DESCRIPTION Removes Appx Packages and Appx Provisioned Packages for All Users .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES 19.12.20 David Segura @SeguraOSD #> function Remove-AppxOnline { [CmdletBinding()] param ( #Appx Packages selected in GridView will be removed from the Windows Image [System.Management.Automation.SwitchParameter]$GridRemoveAppx, #Appx Provisioned Packages selected in GridView will be removed from the Windows Image [System.Management.Automation.SwitchParameter]$GridRemoveAppxPP, #Appx Packages matching the string will be removed [string[]]$Name ) begin { #================================================= # Blocks #================================================= Block-StandardUser Block-WindowsVersionNe10 #================================================= } process { #================================================= # AppxPackage #================================================= if (Get-Command Get-AppxPackage) { if ($GridRemoveAppx.IsPresent) { Get-AppxPackage | Select-Object * | Where-Object {$_.NonRemovable -ne $true} | Out-GridView -PassThru -Title "Select Appx Packages to Remove from Online Windows Image" | ForEach-Object { Write-Verbose "$($_.Name): Removing Appx Package $($_.PackageFullName)" -Verbose Remove-AppPackage -AllUsers -Package $_.PackageFullName -Verbose } } } #================================================= # AppxProvisionedPackage #================================================= if (Get-Command Get-AppxProvisionedPackage) { if ($GridRemoveAppxPP.IsPresent) { Get-AppxProvisionedPackage -Online | Select-Object DisplayName, PackageName | Out-GridView -PassThru -Title "Select Appx Provisioned Packages to Remove from Online Windows Image" | ForEach-Object { Write-Verbose "$($_.DisplayName): Removing Appx Provisioned Package $($_.PackageName)" -Verbose Remove-AppProvisionedPackage -Online -AllUsers -PackageName $_.PackageName } } } #================================================= # RemoveAppx #================================================= foreach ($Item in $Name) { if (Get-Command Get-AppxPackage) { if ((Get-Command Get-AppxPackage).Parameters.ContainsKey('AllUsers')) { Get-AppxPackage -AllUsers | Select-Object * | Where-Object {$_.NonRemovable -ne $true} | Where-Object {$_.Name -Match $Item} | ForEach-Object { Write-Host -ForegroundColor DarkCyan $_.Name if ((Get-Command Remove-AppxPackage).Parameters.ContainsKey('AllUsers')) { Try {Remove-AppxPackage -AllUsers -Package $_.PackageFullName | Out-Null} Catch { #Write-Warning "AllUsers Appx Package $($_.PackageFullName) did not remove successfully" } } else { Try {Remove-AppxPackage -Package $_.PackageFullName | Out-Null} Catch { #Write-Warning "Appx Package $($_.PackageFullName) did not remove successfully" } } } } else { Get-AppxPackage | Select-Object * | Where-Object {$_.NonRemovable -ne $true} | Where-Object {$_.Name -Match $Item} | ForEach-Object { Write-Host -ForegroundColor DarkCyan $_.Name if ((Get-Command Remove-AppxPackage).Parameters.ContainsKey('AllUsers')) { Try {Remove-AppxPackage -AllUsers -Package $_.PackageFullName | Out-Null} Catch { #Write-Warning "AllUsers Appx Package $($_.PackageFullName) did not remove successfully" } } else { Try {Remove-AppxPackage -Package $_.PackageFullName | Out-Null} Catch { #Write-Warning "Appx Package $($_.PackageFullName) did not remove successfully" } } } } } if (Get-Command Get-AppxProvisionedPackage) { Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -Match $Item} | ForEach-Object { Write-Host -ForegroundColor DarkCyan $_.Name if ((Get-Command Remove-AppxProvisionedPackage).Parameters.ContainsKey('AllUsers')) { Try {Remove-AppxProvisionedPackage -Online -AllUsers -PackageName $_.PackageName | Out-Null} Catch { #Write-Warning "AllUsers Appx Provisioned Package $($_.PackageName) did not remove successfully" } } else { Try {Remove-AppxProvisionedPackage -Online -PackageName $_.PackageName | Out-Null} Catch { #Write-Warning "Appx Provisioned Package $($_.PackageName) did not remove successfully" } } } } } } end {} } <# .SYNOPSIS Returns True if ImagePath is a Windows Image .DESCRIPTION Returns True if ImagePath is a Windows Image .PARAMETER ImagePath Specifies the full path to the Windows Image .PARAMETER Index Index of the Windows Image .PARAMETER Extension Test if the File Extension is .esd or .wim .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES #> function Test-WindowsImage { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [Alias('FullName')] [string]$ImagePath, [Parameter( ValueFromPipelineByPropertyName )] [Alias('ImageIndex')] [UInt32]$Index = $null, [Parameter( ValueFromPipelineByPropertyName )] [ValidateSet('.esd','.wim')] [string]$Extension = $null ) #================================================= # Test-Path #================================================= if (! (Test-Path $ImagePath)) { Write-Warning "Test-WindowsImage: Test-Path failed $ImagePath" Return $false } #================================================= # Get-Item #================================================= try { $GetItem = Get-Item -Path $ImagePath -ErrorAction Stop } catch { Write-Warning "Test-WindowsImage: Get-Item failed $ImagePath" Return $false } #================================================= # Get-Item Extension #================================================= if ($Extension) { if (($Extension -eq '.esd') -and ($GetItem.Extension -ne '.esd')) { Write-Warning "Test-WindowsImage: Get-Item Extension is not $Extension" Return $false } if (($Extension -eq '.wim') -and ($GetItem.Extension -ne '.wim')) { Write-Warning "Test-WindowsImage: Get-Item Extension is not $Extension" Return $false } } else { if (($GetItem.Extension -ne '.esd') -and ($GetItem.Extension -ne '.wim')) { Write-Warning "Test-WindowsImage: Get-Item Extension failed. File must be .esd or .wim" Return $false } } #================================================= # Get-WindowsImage #================================================= if ($Index) { try { $GetWindowsImage = Get-WindowsImage -ImagePath $GetItem.FullName -Index $Index -ErrorAction Stop | Out-Null Return $true } catch { Write-Warning "Test-WindowsImage: Get-WindowsImage failed $ImagePath" Return $false } finally { $Error.Clear() } } else { try { $GetWindowsImage = Get-WindowsImage -ImagePath $GetItem.FullName -ErrorAction Stop | Out-Null Return $true } catch { Write-Warning "Test-WindowsImage: Get-WindowsImage failed $ImagePath" Return $false } finally { $Error.Clear() } } #================================================= # Something didn't work right if this is run #================================================= Return $false #================================================= } <# .SYNOPSIS Returns True if ImagePath is Mounted .DESCRIPTION Returns True if ImagePath is Mounted .PARAMETER ImagePath Specifies the full path to the Windows Image .PARAMETER Index Index of the Windows Image .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES #> function Test-WindowsImageMounted { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName )] [string]$ImagePath, [Parameter(ValueFromPipelineByPropertyName)] [UInt32]$Index = 1 ) if (Get-WindowsImage -Mounted | Where-Object {($_.ImagePath -eq $ImagePath) -and ($_.ImageIndex -eq $Index)}) { Return $true } else { Return $false } } <# .SYNOPSIS Returns True if Path is a Windows Image mount directory .DESCRIPTION Returns True if Path is a Windows Image mount directory .PARAMETER Path Full Path to a Windows Image mount directory .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES #> function Test-WindowsImageMountPath { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName )] [string]$Path ) if (Get-WindowsImage -Mounted | Where-Object {$_.Path -eq $Path}) { Return $true } else { Return $false } } <# .SYNOPSIS OSDBuilder function that tests the LCU and returns the Package Type .DESCRIPTION OSDBuilder function that tests the LCU and returns the Package Type .PARAMETER PackagePath Path to the Windows update package to test .PARAMETER Path Directory path where the Windows Image is mounted .LINK https://www.osdcloud.com .NOTES Credit to Lasse Meggele @lassemeggele for correcting some issues. Thanks! #> function Test-WindowsPackageCAB { [CmdletBinding()] param ( [Parameter(Mandatory)] [System.String] $PackagePath, [Parameter()] [System.String] $Path ) try { $WinPackage = $null if ($Path) { $WinPackage = Get-WindowsPackage -Path $Path -PackagePath $PackagePath -ErrorAction SilentlyContinue } else { $WinPackage = Get-WindowsPackage -Online -PackagePath $PackagePath -ErrorAction SilentlyContinue } } catch { Write-Verbose -Message $_.Exception.Message } Write-Verbose -Message $PackagePath Write-Verbose -Message $Path [string]$returnVal = [string]::Empty if ([string]::IsNullOrWhiteSpace($WinPackage.PackageName)) { Write-Verbose -Message 'Could not extract PackageName from PackagePath.' } else { switch ($WinPackage.PackageName) { { $_ -match 'OnePackage' } { $returnVal = 'CombinedMSU'; Break; } { $_ -match 'Multiple_Packages' } { $returnVal = 'CombinedLCU'; Break; } { $_ -match 'DotNetRollup' } { $returnVal = 'DotNetCU'; Break; } { $_ -match 'ServicingStack' } { $returnVal = 'SSU'; Break; } Default { $returnVal = $WinPackage.PackageName; Break; } } Write-Verbose -Message $returnVal } Return $returnVal } <# .SYNOPSIS Updates a mounted WIM .DESCRIPTION Updates a mounted WIM files. Requires WSUSXML Catalog .PARAMETER Path Specifies the full path to the root directory of the offline Windows image that you will service .PARAMETER Update Check or Install the specified Update Group Check = Validate installed Updates All = Install all required Updates AdobeSU = Adobe Security Update DotNet = DotNet Update DotNetCU = DotNet Cumulative Update LCU = Latest Cumulative Update SSU = Servicing Stack Update .PARAMETER BitsTransfer Download the file using BITS-Transfer Interactive Login required .PARAMETER Force Updates are only installed if they are needed Force parameter will install the update even if it is already installed .LINK https://github.com/OSDeploy/OSD/tree/master/Docs .NOTES #> function Update-MyWindowsImage { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [System.String[]]$Path, [ValidateSet('Check','All','AdobeSU','DotNet','DotNetCU','LCU','SSU')] [System.String]$Update = 'Check', [System.Management.Automation.SwitchParameter]$BitsTransfer, [System.Management.Automation.SwitchParameter]$Force ) begin { #================================================= # Block #================================================= Block-StandardUser Block-WindowsVersionNe10 #================================================= # Get-WindowsImage Mounted #================================================= if ($null -eq $Path) { $Path = (Get-WindowsImage -Mounted | Select-Object -Property Path).Path } #================================================= } process { foreach ($Input in $Path) { #================================================= # Path #================================================= $MountPath = (Get-Item -Path $Input | Select-Object FullName).FullName Write-Verbose "Path: $MountPath" -Verbose #================================================= # Validate Mount Path #================================================= if (-not (Test-Path $Input -ErrorAction SilentlyContinue)) { Write-Warning "Update-MyWindowsImage: Unable to locate Mounted WindowsImage at $Input" Break } #================================================= # Get Registry Information #================================================= $global:GetRegCurrentVersion = Get-RegCurrentVersion -Path $Input #================================================= # Require OSMajorVersion 10 #================================================= if ($global:GetRegCurrentVersion.CurrentMajorVersionNumber -ne 10) { Write-Warning "Update-MyWindowsImage: OS MajorVersion 10 is required" Break } Write-Verbose -Verbose $global:GetRegCurrentVersion.ReleaseId #================================================= # Get-WSUSXML and Filter Results #================================================= $global:GetWSUSXML = Get-WSUSXML -Catalog Windows -Silent | Sort-Object UpdateGroup -Descending if ($global:GetRegCurrentVersion.ReleaseId -gt 0) { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateBuild -eq $global:GetRegCurrentVersion.DisplayVersion} } else { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateBuild -eq $global:GetRegCurrentVersion.ReleaseId} } if ($global:GetRegCurrentVersion.BuildLabEx -match 'amd64') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateArch -eq 'x64'} } else { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateArch -eq 'x86'} } if ($global:GetRegCurrentVersion.InstallationType -match 'WindowsPE') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateOS -eq 'Windows 10'} $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateGroup -notmatch 'Adobe'} $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateGroup -notmatch 'DotNet'} } if ($global:GetRegCurrentVersion.InstallationType -match 'Core') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateGroup -notmatch 'Adobe'} } if ($global:GetRegCurrentVersion.InstallationType -match 'Client') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateOS -notmatch 'Server'} } if ($global:GetRegCurrentVersion.InstallationType -match 'Server') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateOS -match 'Server'} } #Don't install Optional Updates $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateGroup -ne ''} if ($Update -ne 'Check' -and $Update -ne 'All') { $global:GetWSUSXML = $global:GetWSUSXML | Where-Object {$_.UpdateGroup -match $Update} } #================================================= # Get-SessionsXml #================================================= $global:GetSessionsXml = Get-SessionsXml -Path "$Input" | Where-Object {$_.targetState -eq 'Installed'} | Sort-Object id #================================================= # Apply Update #================================================= foreach ($item in $global:GetWSUSXML) { if (! ($Force.IsPresent)) { if ($global:GetSessionsXml | Where-Object {$_.KBNumber -match "$($item.FileKBNumber)"}) { Write-Verbose "Installed: $($item.Title) $($item.FileName)" -Verbose Continue } else { Write-Warning "Not Installed: $($item.Title) $($item.FileName)" } } if ($Update -eq 'Check') {Continue} <# if ($BitsTransfer.IsPresent) { $UpdateFile = Save-OSDDownload -SourceUrl $item.OriginUri -BitsTransfer -Verbose } else { $UpdateFile = Save-OSDDownload -SourceUrl $item.OriginUri -Verbose } #> $UpdateFile = Save-WebFile -SourceUrl $item.OriginUri $CurrentLog = "$env:TEMP\OSD\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Update-MyWindowsImage.log" if (! (Test-Path "$env:TEMP\OSD")) {New-Item -Path "$env:TEMP\OSD" -Force | Out-Null} if (Test-Path $UpdateFile.FullName) { #Write-Verbose "Add-WindowsPackage -PackagePath $($UpdateFile.FullName) -Path $Input" -Verbose Try { Write-Verbose "Add-WindowsPackage -Path $Input -PackagePath $($UpdateFile.FullName)" -Verbose Add-WindowsPackage -Path $Input -PackagePath $UpdateFile.FullName -LogPath $CurrentLog | Out-Null } Catch { if ($_.Exception.Message -match '0x800f081e') { Write-Verbose "Update-MyWindowsImage: 0x800f081e The package is not applicable to this image" -Verbose} Write-Verbose $CurrentLog -Verbose } } else { Write-Warning "Unable to download $($UpdateFile.FullName)" } } #================================================= # Return for PassThru #================================================= Get-WindowsImage -Mounted | Where-Object {$_.Path -eq $MountPath} #================================================= } } end {} } |