Private/WWFunctions.ps1
#region Functions Function Get-FormVariables { if ($global:ReadmeDisplay -ne $true) { Write-Host 'If you need to reference this display again, run Get-FormVariables' -ForegroundColor Yellow; $global:ReadmeDisplay = $true } #write-host "Found the following interactable elements from our form" -ForegroundColor Cyan Get-Variable WPF* } #=========================================================================== # Functions for Controls #=========================================================================== # Test for admin Function Test-Admin { $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) $adminRole = [Security.Principal.WindowsBuiltInRole]::Administrator if ($currentUser.IsInRole($adminRole)) { Update-Log -Data 'User has admin privileges' -Class Information } else { Update-Log -Data 'This script requires administrative privileges. Please run it as an administrator.' -Class Error Exit } } #Function to select mounting directory Function Select-MountDir { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Select the mount folder' $null = $browser.ShowDialog() $MountDir = $browser.SelectedPath $WPFMISMountTextBox.text = $MountDir Test-MountPath -path $WPFMISMountTextBox.text Update-Log -Data 'Mount directory selected' -Class Information } #Function to select Source WIM Function Select-SourceWIM { $SourceWIM = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = "$global:workdir\imports\wim" Filter = 'WIM (*.wim)|' } $null = $SourceWIM.ShowDialog() $WPFSourceWIMSelectWIMTextBox.text = $SourceWIM.FileName if ($SourceWIM.FileName -notlike '*.wim') { Update-Log -Data 'A WIM file not selected. Please select a valid file to continue.' -Class Warning return } #Select the index $ImageFull = @(Get-WindowsImage -ImagePath $WPFSourceWIMSelectWIMTextBox.text) $a = $ImageFull | Out-GridView -Title 'Choose an Image Index' -PassThru $IndexNumber = $a.ImageIndex if ($null -eq $indexnumber) { Update-Log -Data 'Index not selected. Reselect the WIM file to select an index' -Class Warning return } Import-WimInfo -IndexNumber $IndexNumber } Function Import-WimInfo($IndexNumber, [switch]$SkipUserConfirmation) { Update-Log -Data 'Importing Source WIM Info' -Class Information try { #Gets WIM metadata to populate fields on the Source tab. $ImageInfo = Get-WindowsImage -ImagePath $WPFSourceWIMSelectWIMTextBox.text -Index $IndexNumber -ErrorAction Stop } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data 'The WIM file selected may be borked. Try a different one' -Class Warning return } $text = 'WIM file selected: ' + $SourceWIM.FileName # $text = "WIM file selected: " + $ImageInfo.FileName Update-Log -data $text -Class Information $text = 'Edition selected: ' + $ImageInfo.ImageName Update-Log -data $text -Class Information $ImageIndex = $IndexNumber $WPFSourceWIMImgDesTextBox.text = $ImageInfo.ImageName $WPFSourceWimVerTextBox.Text = $ImageInfo.Version $WPFSourceWimSPBuildTextBox.text = $ImageInfo.SPBuild $WPFSourceWimLangTextBox.text = $ImageInfo.Languages $WPFSourceWimIndexTextBox.text = $ImageIndex if ($ImageInfo.Architecture -eq 9) { $WPFSourceWimArchTextBox.text = 'x64' } Else { $WPFSourceWimArchTextBox.text = 'x86' } if ($WPFSourceWIMImgDesTextBox.text -like 'Windows Server*') { $WPFJSONEnableCheckBox.IsChecked = $False $WPFAppxCheckBox.IsChecked = $False $WPFAppTab.IsEnabled = $False $WPFAutopilotTab.IsEnabled = $False $WPFMISAppxTextBox.text = 'False' $WPFMISJSONTextBox.text = 'False' $WPFMISOneDriveCheckBox.IsChecked = $False $WPFMISOneDriveCheckBox.IsEnabled = $False } Else { $WPFAppTab.IsEnabled = $True $WPFAutopilotTab.IsEnabled = $True $WPFMISOneDriveCheckBox.IsEnabled = $True } ######right here if ($SkipUserConfirmation -eq $False) { $WPFSourceWimTBVersionNum.text = Get-WinVersionNumber } } #Function to Select JSON File Function Select-JSONFile { $JSON = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Filter = 'JSON (*.JSON)|' } $null = $JSON.ShowDialog() $WPFJSONTextBox.Text = $JSON.FileName $text = 'JSON file selected: ' + $JSON.FileName Update-Log -Data $text -Class Information Invoke-ParseJSON -file $JSON.FileName } #Function to parse the JSON file for user valuable info Function Invoke-ParseJSON($file) { try { Update-Log -Data 'Attempting to parse JSON file...' -Class Information $autopilotinfo = Get-Content $WPFJSONTextBox.Text | ConvertFrom-Json Update-Log -Data 'Successfully parsed JSON file' -Class Information $WPFZtdCorrelationId.Text = $autopilotinfo.ZtdCorrelationId $WPFCloudAssignedTenantDomain.Text = $autopilotinfo.CloudAssignedTenantDomain $WPFComment_File.text = $autopilotinfo.Comment_File } catch { $WPFZtdCorrelationId.Text = 'Bad file. Try Again.' $WPFCloudAssignedTenantDomain.Text = 'Bad file. Try Again.' $WPFComment_File.text = 'Bad file. Try Again.' Update-Log -Data 'Failed to parse JSON file. Try another' return } } #Function to select the paths for the driver fields Function Select-DriverSource($DriverTextBoxNumber) { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Select the Driver Source folder' $null = $browser.ShowDialog() $DriverDir = $browser.SelectedPath $DriverTextBoxNumber.Text = $DriverDir Update-Log -Data "Driver path selected: $DriverDir" -Class Information } #Function to assign the target directory Function Select-TargetDir { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Select the target folder' $null = $browser.ShowDialog() $TargetDir = $browser.SelectedPath $WPFMISWimFolderTextBox.text = $TargetDir #I SCREWED UP THIS VARIABLE Update-Log -Data 'Target directory selected' -Class Information } #Function to enable logging and folder check Function Update-Log { Param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [string]$Data, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [string]$Solution = $Solution, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [validateset('Information', 'Warning', 'Error', 'Comment')] [string]$Class = 'Information' ) $global:ScriptLogFilePath = $Log $LogString = "$(Get-Date) $Class - $Data" $HostString = "$(Get-Date) $Class - $Data" Add-Content -Path $Log -Value $LogString switch ($Class) { 'Information' { Write-Host $HostString -ForegroundColor Gray } 'Warning' { Write-Host $HostString -ForegroundColor Yellow } 'Error' { Write-Host $HostString -ForegroundColor Red } 'Comment' { Write-Host $HostString -ForegroundColor Green } Default { } } #The below line is for a logging tab that was removed. If it gets put back in, reenable the line # $WPFLoggingTextBox.text = Get-Content -Path $Log -Delimiter "\n" } #Removes old log and creates all folders if does not exist Function Set-Logging { #logging folder if (!(Test-Path -Path "$global:workdir\logging\WIMWitch.Log" -PathType Leaf)) { New-Item -ItemType Directory -Force -Path "$global:workdir\Logging" | Out-Null New-Item -Path "$global:workdir\logging" -Name 'WIMWitch.log' -ItemType 'file' -Value '***Logging Started***' | Out-Null } Else { Remove-Item -Path "$global:workdir\logging\WIMWitch.log" New-Item -Path "$global:workdir\logging" -Name 'WIMWitch.log' -ItemType 'file' -Value '***Logging Started***' | Out-Null } #updates folder $FileExist = Test-Path -Path "$global:workdir\updates" #-PathType Leaf if ($FileExist -eq $False) { Update-Log -Data 'Updates folder does not exist. Creating...' -Class Warning New-Item -ItemType Directory -Force -Path "$global:workdir\updates" | Out-Null Update-Log -Data 'Updates folder created' -Class Information } if ($FileExist -eq $True) { Update-Log -Data 'Updates folder exists' -Class Information } #staging folder $FileExist = Test-Path -Path "$global:workdir\Staging" #-PathType Leaf if ($FileExist -eq $False) { Update-Log -Data 'Staging folder does not exist. Creating...' -Class Warning New-Item -ItemType Directory -Force -Path "$global:workdir\Staging" | Out-Null Update-Log -Data 'Staging folder created' -Class Information } if ($FileExist -eq $True) { Update-Log -Data 'Staging folder exists' -Class Information } #Mount folder $FileExist = Test-Path -Path "$global:workdir\Mount" #-PathType Leaf if ($FileExist -eq $False) { Update-Log -Data 'Mount folder does not exist. Creating...' -Class Warning New-Item -ItemType Directory -Force -Path "$global:workdir\Mount" | Out-Null Update-Log -Data 'Mount folder created' -Class Information } if ($FileExist -eq $True) { Update-Log -Data 'Mount folder exists' -Class Information } #Completed WIMs folder $FileExist = Test-Path -Path "$global:workdir\CompletedWIMs" #-PathType Leaf if ($FileExist -eq $False) { Update-Log -Data 'CompletedWIMs folder does not exist. Creating...' -Class Warning New-Item -ItemType Directory -Force -Path "$global:workdir\CompletedWIMs" | Out-Null Update-Log -Data 'CompletedWIMs folder created' -Class Information } if ($FileExist -eq $True) { Update-Log -Data 'CompletedWIMs folder exists' -Class Information } #Configurations XML folder $FileExist = Test-Path -Path "$global:workdir\Configs" #-PathType Leaf if ($FileExist -eq $False) { Update-Log -Data 'Configs folder does not exist. Creating...' -Class Warning New-Item -ItemType Directory -Force -Path "$global:workdir\Configs" | Out-Null Update-Log -Data 'Configs folder created' -Class Information } if ($FileExist -eq $True) { Update-Log -Data 'Configs folder exists' -Class Information } } Function Install-Driver($drivertoapply) { try { Add-WindowsDriver -Path $WPFMISMountTextBox.Text -Driver $drivertoapply -ErrorAction Stop | Out-Null Update-Log -Data "Applied $drivertoapply" -Class Information } catch { Update-Log -Data "Couldn't apply $drivertoapply" -Class Warning } } #Function for injecting drivers into the mounted WIM Function Start-DriverInjection($Folder) { #This filters out invalid paths, such as the default value $testpath = Test-Path $folder -PathType Container If ($testpath -eq $false) { return } If ($testpath -eq $true) { Update-Log -data "Applying drivers from $folder" -class Information Get-ChildItem $Folder -Recurse -Filter '*inf' | ForEach-Object { Install-Driver $_.FullName } Update-Log -Data "Completed driver injection from $folder" -Class Information } } #Function to retrieve OSDUpdate Version Function Get-OSDBInstallation { Update-Log -Data 'Getting OSD Installation information' -Class Information try { Import-Module -Name OSDUpdate -ErrorAction Stop } catch { $WPFUpdatesOSDBVersion.Text = 'Not Installed.' Update-Log -Data 'OSD Update is not installed.' -Class Warning Return } try { $OSDBVersion = Get-Module -Name OSDUpdate -ErrorAction Stop $WPFUpdatesOSDBVersion.Text = $OSDBVersion.Version $text = $osdbversion.version Update-Log -data "Installed version of OSD Update is $text." -Class Information Return } catch { Update-Log -Data 'Unable to fetch OSD Update version.' -Class Error Return } } # Function to retrieve OSDSUS Version Function Get-OSDSUSInstallation { Update-Log -Data 'Getting OSDSUS Installation information' -Class 'Information' try { Import-Module -Name OSDSUS -ErrorAction Stop } catch { $WPFUpdatesOSDSUSVersion.Text = 'Not Installed' Update-Log -Data 'OSDSUS is not installed.' -Class Warning Return } try { $OSDSUSVersion = Get-Module -Name OSDSUS -ErrorAction Stop $WPFUpdatesOSDSUSVersion.Text = $OSDSUSVersion.Version $text = $osdsusversion.version Update-Log -data "Installed version of OSDSUS is $text." -Class Information Return } catch { Update-Log -Data 'Unable to fetch OSDSUS version.' -Class Error Return } } #Function to retrieve current OSDUpdate Version Function Get-OSDBCurrentVer { Update-Log -Data 'Checking for the most current OSDUpdate version available' -Class Information try { $OSDBCurrentVer = Find-Module -Name OSDUpdate -ErrorAction Stop $WPFUpdatesOSDBCurrentVerTextBox.Text = $OSDBCurrentVer.version $text = $OSDBCurrentVer.version Update-Log -data "$text is the most current version" -class Information Return } catch { $WPFUpdatesOSDBCurrentVerTextBox.Text = 'Network Error' Return } } #Function to retrieve current OSDUSUS Version Function Get-OSDSUSCurrentVer { Update-Log -Data 'Checking for the most current OSDSUS version available' -Class Information try { $OSDSUSCurrentVer = Find-Module -Name OSDSUS -ErrorAction Stop $WPFUpdatesOSDSUSCurrentVerTextBox.Text = $OSDSUSCurrentVer.version $text = $OSDSUSCurrentVer.version Update-Log -data "$text is the most current version" -class Information Return } catch { $WPFUpdatesOSDSUSCurrentVerTextBox.Text = 'Network Error' Return } } #Function to update or install OSDUpdate Function Update-OSDB { if ($WPFUpdatesOSDBVersion.Text -eq 'Not Installed') { Update-Log -Data 'Attempting to install and import OSD Update' -Class Information try { Install-Module OSDUpdate -Force -ErrorAction Stop #Write-Host "Installed module" Update-Log -data 'OSD Update module has been installed' -Class Information Import-Module -Name OSDUpdate -Force -ErrorAction Stop #Write-Host "Imported module" Update-Log -Data 'OSD Update module has been imported' -Class Information Update-Log -Data '****************************************************************************' -Class Warning Update-Log -Data 'Please close WIM Witch and all PowerShell windows, then rerun to continue...' -Class Warning Update-Log -Data '****************************************************************************' -Class Warning #$WPFUpdatesOSDBClosePowerShellTextBlock.visibility = "Visible" $WPFUpdatesOSDListBox.items.add('Please close all PowerShell windows, including WIM Witch, then relaunch app to continue') Return } catch { $WPFUpdatesOSDBVersion.Text = 'Inst Fail' Update-Log -Data "Couldn't install OSD Update" -Class Error Update-Log -data $_.Exception.Message -class Error Return } } If ($WPFUpdatesOSDBVersion.Text -gt '1.0.0') { Update-Log -data 'Attempting to update OSD Update' -class Information try { Update-ModuleOSDUpdate -ErrorAction Stop Update-Log -Data 'Updated OSD Update' -Class Information Update-Log -Data '****************************************************************************' -Class Warning Update-Log -Data 'Please close WIM Witch and all PowerShell windows, then rerun to continue...' -Class Warning Update-Log -Data '****************************************************************************' -Class Warning #$WPFUpdatesOSDBClosePowerShellTextBlock.visibility = "Visible" $WPFUpdatesOSDListBox.items.add('Please close all PowerShell windows, including WIM Witch, then relaunch app to continue') get-OSDBInstallation return } catch { $WPFUpdatesOSDBCurrentVerTextBox.Text = 'OSDB Err' Return } } } #Function to update or install OSDSUS Function Update-OSDSUS { if ($WPFUpdatesOSDSUSVersion.Text -eq 'Not Installed') { Update-Log -Data 'Attempting to install and import OSDSUS' -Class Information try { Install-Module OSDUpdate -Force -ErrorAction Stop Update-Log -data 'OSDSUS module has been installed' -Class Information Import-Module -Name OSDUpdate -Force -ErrorAction Stop Update-Log -Data 'OSDSUS module has been imported' -Class Information Update-Log -Data '****************************************************************************' -Class Warning Update-Log -Data 'Please close WIM Witch and all PowerShell windows, then rerun to continue...' -Class Warning Update-Log -Data '****************************************************************************' -Class Warning #$WPFUpdatesOSDBClosePowerShellTextBlock.visibility = "Visible" $WPFUpdatesOSDListBox.items.add('Please close all PowerShell windows, including WIM Witch, then relaunch app to continue') Return } catch { $WPFUpdatesOSDSUSVersion.Text = 'Inst Fail' Update-Log -Data "Couldn't install OSDSUS" -Class Error Update-Log -data $_.Exception.Message -class Error Return } } If ($WPFUpdatesOSDSUSVersion.Text -gt '1.0.0') { Update-Log -data 'Attempting to update OSDSUS' -class Information try { Uninstall-Module -Name osdsus -AllVersions -Force Install-Module -Name osdsus -Force Update-Log -Data 'Updated OSDSUS' -Class Information Update-Log -Data '****************************************************************************' -Class Warning Update-Log -Data 'Please close WIM Witch and all PowerShell windows, then rerun to continue...' -Class Warning Update-Log -Data '****************************************************************************' -Class Warning #$WPFUpdatesOSDBClosePowerShellTextBlock.visibility = "Visible" $WPFUpdatesOSDListBox.items.add('Please close all PowerShell windows, including WIM Witch, then relaunch app to continue') get-OSDSUSInstallation return } catch { $WPFUpdatesOSDSUSCurrentVerTextBox.Text = 'OSDSUS Err' Return } } } #Function to compare OSDBuilder Versions Function Compare-OSDBuilderVer { Update-Log -data 'Comparing OSD Update module versions' -Class Information if ($WPFUpdatesOSDBVersion.Text -eq 'Not Installed') { Return } If ($WPFUpdatesOSDBVersion.Text -eq $WPFUpdatesOSDBCurrentVerTextBox.Text) { Update-Log -Data 'OSD Update is up to date' -class Information Return } #$WPFUpdatesOSDBOutOfDateTextBlock.Visibility = "Visible" $WPFUpdatesOSDListBox.items.add('A software update module is out of date. Please click the Install / Update button to update it.') Update-Log -Data 'OSD Update appears to be out of date. Run the upgrade Function from within WIM Witch to resolve' -class Warning Return } #Function to compare OSDSUS Versions Function Compare-OSDSUSVer { Update-Log -data 'Comparing OSDSUS module versions' -Class Information if ($WPFUpdatesOSDSUSVersion.Text -eq 'Not Installed') { Return } If ($WPFUpdatesOSDSUSVersion.Text -eq $WPFUpdatesOSDSUSCurrentVerTextBox.Text) { Update-Log -Data 'OSDSUS is up to date' -class Information Return } #$WPFUpdatesOSDBOutOfDateTextBlock.Visibility = "Visible" $WPFUpdatesOSDListBox.items.add('A software update module is out of date. Please click the Install / Update button to update it.') | Out-Null Update-Log -Data 'OSDSUS appears to be out of date. Run the upgrade Function from within WIM Witch to resolve' -class Warning Return } #Function to check for superceded updates Function Test-Superceded($action, $OS, $Build) { Update-Log -Data 'Checking WIM Witch Update store for superseded updates' -Class Information $path = $global:workdir + '\updates\' + $OS + '\' + $Build + '\' #sets base path if ((Test-Path -Path $path) -eq $false) { Update-Log -Data 'No updates found, likely not yet downloaded. Skipping supersedense check...' -Class Warning return } $Children = Get-ChildItem -Path $path #query sub directories foreach ($Children in $Children) { $path1 = $path + $Children $sprout = Get-ChildItem -Path $path1 foreach ($sprout in $sprout) { $path3 = $path1 + '\' + $sprout $fileinfo = Get-ChildItem -Path $path3 foreach ($file in $fileinfo) { $StillCurrent = Get-OSDUpdate | Where-Object { $_.FileName -eq $file } If ($null -eq $StillCurrent) { Update-Log -data "$file no longer current" -Class Warning if ($action -eq 'delete') { Update-Log -data "Deleting $path3" -class Warning Remove-Item -Path $path3 -Recurse -Force } if ($action -eq 'audit') { $WPFUpdatesOSDListBox.items.add('Superceded updates discovered. Please select the versions of Windows 10 you are supporting and click Update') Return } } else { Update-Log -data "$file is still current" -Class Information } } } } Update-Log -data 'Supercedense check complete.' -Class Information } #Function to download new patches with OSDSUS Function Get-WindowsPatches($build, $OS) { Update-Log -Data "Downloading SSU updates for $OS $build" -Class Information try { Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'SSU' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\SSU } catch { Update-Log -data 'Failed to download SSU update' -Class Error Update-Log -data $_.Exception.Message -class Error } Update-Log -Data "Downloading AdobeSU updates for $OS $build" -Class Information try { Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'AdobeSU' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\AdobeSU } catch { Update-Log -data 'Failed to download AdobeSU update' -Class Error Update-Log -data $_.Exception.Message -class Error } Update-Log -Data "Downloading LCU updates for $OS $build" -Class Information try { Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'LCU' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\LCU } catch { Update-Log -data 'Failed to download LCU update' -Class Error Update-Log -data $_.Exception.Message -class Error } Update-Log -Data "Downloading .Net updates for $OS $build" -Class Information try { Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'DotNet' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\DotNet } catch { Update-Log -data 'Failed to download .Net update' -Class Error Update-Log -data $_.Exception.Message -class Error } Update-Log -Data "Downloading .Net CU updates for $OS $build" -Class Information try { Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'DotNetCU' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\DotNetCU } catch { Update-Log -data 'Failed to download .Net CU update' -Class Error Update-Log -data $_.Exception.Message -class Error } if ($WPFUpdatesCBEnableOptional.IsChecked -eq $True) { try { Update-Log -Data "Downloading optional updates for $OS $build" -Class Information Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'Optional' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\Optional } catch { Update-Log -data 'Failed to download optional update' -Class Error Update-Log -data $_.Exception.Message -class Error } } if ($WPFUpdatesCBEnableDynamic.IsChecked -eq $True) { try { Update-Log -Data "Downloading dynamic updates for $OS $build" -Class Information Get-OSDUpdate -ErrorAction Stop | Where-Object { $_.UpdateOS -eq $OS -and $_.UpdateArch -eq 'x64' -and $_.UpdateBuild -eq $build -and $_.UpdateGroup -eq 'SetupDU' } | Get-DownOSDUpdate -DownloadPath $global:workdir\updates\$OS\$build\Dynamic } catch { Update-Log -data 'Failed to download dynamic update' -Class Error Update-Log -data $_.Exception.Message -class Error } } Update-Log -Data "Downloading completed for $OS $build" -Class Information } #Function to remove superceded updates and initate new patch download Function Update-PatchSource { Update-Log -Data 'attempting to start download Function' -Class Information if ($WPFUSCBSelectCatalogSource.SelectedItem -eq 'OSDSUS') { if ($WPFUpdatesW10Main.IsChecked -eq $true) { if ($WPFUpdatesW10_22H2.IsChecked -eq $true) { Test-Superceded -action delete -build 22H2 -OS 'Windows 10' Get-WindowsPatches -build 22H2 -OS 'Windows 10' } if ($WPFUpdatesW10_21H2.IsChecked -eq $true) { Test-Superceded -action delete -build 21H2 -OS 'Windows 10' Get-WindowsPatches -build 21H2 -OS 'Windows 10' } if ($WPFUpdatesW10_21H1.IsChecked -eq $true) { Test-Superceded -action delete -build 21H1 -OS 'Windows 10' Get-WindowsPatches -build 21H1 -OS 'Windows 10' } if ($WPFUpdatesW10_20H2.IsChecked -eq $true) { Test-Superceded -action delete -build 20H2 -OS 'Windows 10' Get-WindowsPatches -build 20H2 -OS 'Windows 10' } if ($WPFUpdatesW10_2004.IsChecked -eq $true) { Test-Superceded -action delete -build 2004 -OS 'Windows 10' Get-WindowsPatches -build 2004 -OS 'Windows 10' } if ($WPFUpdatesW10_1909.IsChecked -eq $true) { Test-Superceded -action delete -build 1909 -OS 'Windows 10' Get-WindowsPatches -build 1909 -OS 'Windows 10' } if ($WPFUpdatesW10_1903.IsChecked -eq $true) { Test-Superceded -action delete -build 1903 -OS 'Windows 10' Get-WindowsPatches -build 1903 -OS 'Windows 10' } if ($WPFUpdatesW10_1809.IsChecked -eq $true) { Test-Superceded -action delete -build 1809 -OS 'Windows 10' Get-WindowsPatches -build 1809 -OS 'Windows 10' } if ($WPFUpdatesW10_1803.IsChecked -eq $true) { Test-Superceded -action delete -build 1803 -OS 'Windows 10' Get-WindowsPatches -build 1803 -OS 'Windows 10' } if ($WPFUpdatesW10_1709.IsChecked -eq $true) { Test-Superceded -action delete -build 1709 -OS 'Windows 10' Get-WindowsPatches -build 1709 -OS 'Windows 10' } } if ($WPFUpdatesS2019.IsChecked -eq $true) { Test-Superceded -action delete -build 1809 -OS 'Windows Server' Get-WindowsPatches -build 1809 -OS 'Windows Server' } if ($WPFUpdatesS2016.IsChecked -eq $true) { Test-Superceded -action delete -build 1607 -OS 'Windows Server' Get-WindowsPatches -build 1607 -OS 'Windows Server' } if ($WPFUpdatesS2022.IsChecked -eq $true) { Test-Superceded -action delete -build 21H2 -OS 'Windows Server' Get-WindowsPatches -build 21H2 -OS 'Windows Server' } if ($WPFUpdatesW11Main.IsChecked -eq $true) { if ($WPFUpdatesW11_22H2.IsChecked -eq $true) { Test-Superceded -action delete -build 22H2 -OS 'Windows 11' Get-WindowsPatches -build 22H2 -OS 'Windows 11' } if ($WPFUpdatesW11_21h2.IsChecked -eq $true) { Write-Host '21H2' Test-Superceded -action delete -build 21H2 -OS 'Windows 11' Get-WindowsPatches -build 21H2 -OS 'Windows 11' } if ($WPFUpdatesW11_23h2.IsChecked -eq $true) { Write-Host '23H2' Test-Superceded -action delete -build 23H2 -OS 'Windows 11' Get-WindowsPatches -build 23H2 -OS 'Windows 11' } } Get-OneDrive } if ($WPFUSCBSelectCatalogSource.SelectedItem -eq 'ConfigMgr') { if ($WPFUpdatesW10Main.IsChecked -eq $true) { if ($WPFUpdatesW10_22H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '22H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '22H2' } if ($WPFUpdatesW10_21H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '21H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '21H2' } if ($WPFUpdatesW10_21H1.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '21H1' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '21H1' } if ($WPFUpdatesW10_20H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '20H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '20H2' } if ($WPFUpdatesW10_2004.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '2004' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '2004' } if ($WPFUpdatesW10_1909.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '1909' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '1909' } if ($WPFUpdatesW10_1903.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '1903' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '1903' } if ($WPFUpdatesW10_1809.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '1809' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '1809' } if ($WPFUpdatesW10_1803.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '1803' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '1803' } if ($WPFUpdatesW10_1709.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 10' -Ver '1709' Invoke-MEMCMUpdatecatalog -prod 'Windows 10' -ver '1709' } #Get-OneDrive } if ($WPFUpdatesS2019.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows Server' -Ver '1809' Invoke-MEMCMUpdatecatalog -prod 'Windows Server' -Ver '1809' } if ($WPFUpdatesS2016.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows Server' -Ver '1607' Invoke-MEMCMUpdatecatalog -prod 'Windows Server' -Ver '1607' } if ($WPFUpdatesS2022.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows Server' -Ver '21H2' Invoke-MEMCMUpdatecatalog -prod 'Windows Server' -Ver '21H2' } if ($WPFUpdatesW11Main.IsChecked -eq $true) { if ($WPFUpdatesW11_21H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 11' -Ver '21H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 11' -ver '21H2' } if ($WPFUpdatesW11_22H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 11' -Ver '22H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 11' -ver '22H2' } if ($WPFUpdatesW11_23H2.IsChecked -eq $true) { Invoke-MEMCMUpdateSupersedence -prod 'Windows 11' -Ver '23H2' Invoke-MEMCMUpdatecatalog -prod 'Windows 11' -ver '23H2' } } Get-OneDrive } Update-Log -data 'All downloads complete' -class Information } Function Deploy-LCU($packagepath) { $osver = Get-WindowsType if ($osver -eq 'Windows 10') { $executable = "$env:windir\system32\expand.exe" $filename = (Get-ChildItem $packagepath).name Update-Log -Data 'Extracting LCU Package content to staging folder...' -Class Information Start-Process $executable -args @("`"$packagepath\$filename`"", '/f:*.CAB', "`"$global:workdir\staging`"") -Wait -ErrorAction Stop $cabs = (Get-Item $global:workdir\staging\*.cab) #MMSMOA2022 Update-Log -data 'Applying SSU...' -class information foreach ($cab in $cabs) { if ($cab -like '*SSU*') { Update-Log -data $cab -class Information if ($demomode -eq $false) { Add-WindowsPackage -Path $WPFMISMountTextBox.Text -PackagePath $cab -ErrorAction stop | Out-Null } else { $string = 'Demo mode active - Not applying ' + $cab Update-Log -data $string -Class Warning } } } Update-Log -data 'Applying LCU...' -class information foreach ($cab in $cabs) { if ($cab -notlike '*SSU*') { Update-Log -data $cab -class information if ($demomode -eq $false) { Add-WindowsPackage -Path $WPFMISMountTextBox.Text -PackagePath $cab -ErrorAction stop | Out-Null } else { $string = 'Demo mode active - Not applying ' + $cab Update-Log -data $string -Class Warning } } } } if ($osver -eq 'Windows 11') { # Copy file to staging Update-Log -data 'Copying LCU file to staging folder...' -class information $filename = (Get-ChildItem -Path $packagepath -Name) Copy-Item -Path $packagepath\$filename -Destination $global:workdir\staging -Force Update-Log -data 'Changing file extension type from CAB to MSU...' -class information $basename = (Get-Item -Path $global:workdir\staging\$filename).BaseName $newname = $basename + '.msu' Rename-Item -Path $global:workdir\staging\$filename -NewName $newname Update-Log -data 'Applying LCU...' -class information Update-Log -data $global:workdir\staging\$newname -class information $updatename = (Get-Item -Path $packagepath).name Update-Log -data $updatename -Class Information try { if ($demomode -eq $false) { Add-WindowsPackage -Path $WPFMISMountTextBox.Text -PackagePath $global:workdir\staging\$newname -ErrorAction Stop | Out-Null } else { $string = 'Demo mode active - Not applying ' + $updatename Update-Log -data $string -Class Warning } } catch { Update-Log -data 'Failed to apply update' -class Warning Update-Log -data $_.Exception.Message -class Warning } } } #Function to apply updates to mounted WIM Function Deploy-Updates($class) { if (($class -eq 'AdobeSU') -and ($WPFSourceWIMImgDesTextBox.text -like 'Windows Server 20*') -and ($WPFSourceWIMImgDesTextBox.text -notlike '*(Desktop Experience)')) { Update-Log -Data 'Skipping Adobe updates for Server Core build' -Class Information return } $OS = Get-WindowsType $buildnum = Get-WinVersionNumber if ($buildnum -eq '2009') { $buildnum = '20H2' } If (($WPFSourceWimVerTextBox.text -like '10.0.18362.*') -and (($class -ne 'Dynamic') -and ($class -notlike 'PE*'))) { $mountdir = $WPFMISMountTextBox.Text reg LOAD HKLM\OFFLINE $mountdir\Windows\System32\Config\SOFTWARE | Out-Null $regvalues = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\OFFLINE\Microsoft\Windows NT\CurrentVersion\' ) $buildnum = $regvalues.ReleaseId reg UNLOAD HKLM\OFFLINE | Out-Null } If (($WPFSourceWimVerTextBox.text -like '10.0.18362.*') -and (($class -eq 'Dynamic') -or ($class -like 'PE*'))) { $windowsver = Get-WindowsImage -ImagePath ($global:workdir + '\staging\' + $WPFMISWimNameTextBox.text) -Index 1 $Vardate = (Get-Date -Year 2019 -Month 10 -Day 01) if ($windowsver.CreatedTime -gt $vardate) { $buildnum = 1909 } else { $buildnum = 1903 } } if ($class -eq 'PESSU') { $IsPE = $true $class = 'SSU' } if ($class -eq 'PELCU') { $IsPE = $true $class = 'LCU' } $path = $global:workdir + '\updates\' + $OS + '\' + $buildnum + '\' + $class + '\' if ((Test-Path $path) -eq $False) { Update-Log -data "$path does not exist. There are no updates of this class to apply" -class Warning return } $Children = Get-ChildItem -Path $path foreach ($Child in $Children) { $compound = $Child.fullname Update-Log -Data "Applying $Child" -Class Information try { if ($class -eq 'Dynamic') { #Update-Log -data "Applying Dynamic to media" -Class Information $mediafolder = $global:workdir + '\staging\media\sources' $DynUpdates = (Get-ChildItem -Path $compound -Name) foreach ($DynUpdate in $DynUpdates) { $text = $compound + '\' + $DynUpdate #write-host $text Start-Process -FilePath c:\windows\system32\expand.exe -args @("`"$text`"", '-F:*', "`"$mediafolder`"") -Wait } } elseif ($IsPE -eq $true) { Add-WindowsPackage -Path ($global:workdir + '\staging\mount') -PackagePath $compound -ErrorAction stop | Out-Null } else { if ($class -eq 'LCU') { if (($os -eq 'Windows 10') -and (($buildnum -eq '2004') -or ($buildnum -eq '2009') -or ($buildnum -eq '20H2') -or ($buildnum -eq '21H1') -or ($buildnum -eq '21H2') -or ($buildnum -eq '22H2'))) { Update-Log -data 'Processing the LCU package to retrieve SSU...' -class information Deploy-LCU -packagepath $compound } elseif ($os -eq 'Windows 11') { Update-Log -data 'Windows 11 required LCU modification started...' -Class Information Deploy-LCU -packagepath $compound } else { Add-WindowsPackage -Path $WPFMISMountTextBox.Text -PackagePath $compound -ErrorAction stop | Out-Null } } else { Add-WindowsPackage -Path $WPFMISMountTextBox.Text -PackagePath $compound -ErrorAction stop | Out-Null } } } catch { Update-Log -data 'Failed to apply update' -class Warning Update-Log -data $_.Exception.Message -class Warning } } } #Function to select AppX packages to yank Function Select-Appx { $AssetsPath = Join-Path -Path $PSScriptRoot -ChildPath 'Assets' $OS = Get-WindowsType $buildnum = $WPFSourceWimTBVersionNum.text if ($OS -eq 'Windows 10') { $OS = 'Win10' } if ($OS -eq 'Windows 11') { $OS = 'Win11' } $appxListFile = Join-Path -Path $AssetsPath -ChildPath $("appx$OS" + '_' + "$buildnum.txt") Update-Log -Data "Looking for Appx list file $appxListFile" -Class Information if (Test-Path $appxListFile) { $appxPackages = Get-Content $appxListFile $exappxs = $appxPackages | Out-GridView -Title 'Select apps to remove' -PassThru } else { Write-Warning "No matching Appx list file found for build $buildnum." return } if ($null -eq $exappxs) { Update-Log -Data 'No apps were selected' -Class Warning } elseif ($null -ne $exappxs) { Update-Log -data 'The following apps were selected for removal:' -Class Information Foreach ($exappx in $exappxs) { Update-Log -Data $exappx -Class Information } $WPFAppxTextBox.Text = $exappxs -join "`r`n" return $exappxs } } #Function to remove appx packages Function Remove-Appx($array) { $exappxs = $array Update-Log -data 'Starting AppX removal' -class Information foreach ($exappx in $exappxs) { try { Remove-AppxProvisionedPackage -Path $WPFMISMountTextBox.Text -PackageName $exappx -ErrorAction Stop | Out-Null Update-Log -data "Removing $exappx" -Class Information } catch { Update-Log -Data "Failed to remove $exappx" -Class Error Update-Log -Data $_.Exception.Message -Class Error } } return } #Function to remove unwanted image indexes Function Remove-OSIndex { Update-Log -Data 'Attempting to remove unwanted image indexes' -Class Information $wimname = Get-Item -Path $global:workdir\Staging\*.wim Update-Log -Data "Found Image $wimname" -Class Information $IndexesAll = Get-WindowsImage -ImagePath $wimname | ForEach-Object { $_.ImageName } $IndexSelected = $WPFSourceWIMImgDesTextBox.Text foreach ($Index in $IndexesAll) { Update-Log -data "$Index is being evaluated" If ($Index -eq $IndexSelected) { Update-Log -Data "$Index is the index we want to keep. Skipping." -Class Information | Out-Null } else { Update-Log -data "Deleting $Index from WIM" -Class Information Remove-WindowsImage -ImagePath $wimname -Name $Index -InformationAction SilentlyContinue | Out-Null } } } #Function to select which folder to save Autopilot JSON file to Function Select-NewJSONDir { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Select the folder to save JSON' $null = $browser.ShowDialog() $SaveDir = $browser.SelectedPath $WPFJSONTextBoxSavePath.text = $SaveDir $text = "Autopilot profile save path selected: $SaveDir" Update-Log -Data $text -Class Information } Function Update-Autopilot { Update-Log -Data 'Uninstalling old WindowsAutopilotIntune module...' -Class Warning Uninstall-Module -Name WindowsAutopilotIntune -AllVersions Update-Log -Data 'Installing new WindowsAutopilotIntune module...' -Class Warning Install-Module -Name WindowsAutopilotIntune -Force $AutopilotUpdate = ([System.Windows.MessageBox]::Show('WIM Witch needs to close and PowerShell needs to be restarted. Click OK to close WIM Witch.', 'Updating complete.', 'OK', 'warning')) if ($AutopilotUpdate -eq 'OK') { $form.Close() exit } } #Function to retrieve autopilot profile from intune Function Get-WWAutopilotProfile($login, $path) { Update-Log -data 'Checking dependencies for Autopilot profile retrieval...' -Class Information try { Import-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -ErrorAction Stop Update-Log -Data 'NuGet is installed' -Class Information } catch { Update-Log -data 'NuGet is not installed. Installing now...' -Class Warning Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Update-Log -data 'NuGet is now installed' -Class Information } try { Import-Module -Name AzureAD -ErrorAction Stop | Out-Null Update-Log -data 'AzureAD Module is installed' -Class Information } catch { Update-Log -data 'AzureAD Module is not installed. Installing now...' -Class Warning Install-Module AzureAD -Force Update-Log -data 'AzureAD is now installed' -class Information } try { Import-Module -Name WindowsAutopilotIntune -ErrorAction Stop Update-Log -data 'WindowsAutopilotIntune module is installed' -Class Information } catch { Update-Log -data 'WindowsAutopilotIntune module is not installed. Installing now...' -Class Warning Install-Module WindowsAutopilotIntune -Force Update-Log -data 'WindowsAutopilotIntune module is now installed.' -class Information } $AutopilotInstalledVer = (Get-Module -Name windowsautopilotintune).Version Update-Log -Data "The currently installed version of the WindowsAutopilotIntune module is $AutopilotInstalledVer" -Class Information $AutopilotLatestVersion = (Find-Module -Name windowsautopilotintune).version Update-Log -data "The latest available version of the WindowsAutopilotIntune module is $AutopilotLatestVersion" -Class Information if ($AutopilotInstalledVer -eq $AutopilotLatestVersion) { Update-Log -data 'WindowsAutopilotIntune module is current. Continuing...' -Class Information } else { Update-Log -data 'WindowsAutopilotIntune module is out of date. Prompting the user to upgrade...' $UpgradeAutopilot = ([System.Windows.MessageBox]::Show("Would you like to update the WindowsAutopilotIntune module to version $AutopilotLatestVersion now?", 'Update Autopilot Module?', 'YesNo', 'warning')) } if ($UpgradeAutopilot -eq 'Yes') { Update-Log -Data 'User has chosen to update WindowsAutopilotIntune module' -Class Warning Update-Autopilot } elseif ($AutopilotInstalledVer -ne $AutopilotLatestVersion) { Update-Log -data 'User declined to update WindowsAutopilotIntune module. Continuing...' -Class Warning } Update-Log -data 'Connecting to Intune...' -Class Information if ($AutopilotInstalledVer -lt 3.9) { Connect-AutopilotIntune | Out-Null } else { Connect-MSGraph | Out-Null } Update-Log -data 'Connected to Intune' -Class Information Update-Log -data 'Retrieving profile...' -Class Information Get-AutoPilotProfile | Out-GridView -Title 'Select Autopilot profile' -PassThru | ConvertTo-AutoPilotConfigurationJSON | Out-File $path\AutopilotConfigurationFile.json -Encoding ASCII $text = $path + '\AutopilotConfigurationFile.json' Update-Log -data "Profile successfully created at $text" -Class Information } #Function to save current configuration Function Save-Configuration { Param( [parameter(mandatory = $false, HelpMessage = 'config file')] [string]$filename, [parameter(mandatory = $false, HelpMessage = 'enable CM files')] [switch]$CM ) $CurrentConfig = @{ SourcePath = $WPFSourceWIMSelectWIMTextBox.text SourceIndex = $WPFSourceWimIndexTextBox.text SourceEdition = $WPFSourceWIMImgDesTextBox.text UpdatesEnabled = $WPFUpdatesEnableCheckBox.IsChecked AutopilotEnabled = $WPFJSONEnableCheckBox.IsChecked AutopilotPath = $WPFJSONTextBox.text DriversEnabled = $WPFDriverCheckBox.IsChecked DriverPath1 = $WPFDriverDir1TextBox.text DriverPath2 = $WPFDriverDir2TextBox.text DriverPath3 = $WPFDriverDir3TextBox.text DriverPath4 = $WPFDriverDir4TextBox.text DriverPath5 = $WPFDriverDir5TextBox.text AppxIsEnabled = $WPFAppxCheckBox.IsChecked AppxSelected = $WPFAppxTextBox.Text WIMName = $WPFMISWimNameTextBox.text WIMPath = $WPFMISWimFolderTextBox.text MountPath = $WPFMISMountTextBox.text DotNetEnabled = $WPFMISDotNetCheckBox.IsChecked OneDriveEnabled = $WPFMISOneDriveCheckBox.IsChecked LPsEnabled = $WPFCustomCBLangPacks.IsChecked LXPsEnabled = $WPFCustomCBLEP.IsChecked FODsEnabled = $WPFCustomCBFOD.IsChecked LPListBox = $WPFCustomLBLangPacks.items LXPListBox = $WPFCustomLBLEP.Items FODListBox = $WPFCustomLBFOD.Items PauseAfterMount = $WPFMISCBPauseMount.IsChecked PauseBeforeDM = $WPFMISCBPauseDismount.IsChecked RunScript = $WPFCustomCBRunScript.IsChecked ScriptTiming = $WPFCustomCBScriptTiming.SelectedItem ScriptFile = $WPFCustomTBFile.Text ScriptParams = $WPFCustomTBParameters.Text CMImageType = $WPFCMCBImageType.SelectedItem CMPackageID = $WPFCMTBPackageID.Text CMImageName = $WPFCMTBImageName.Text CMVersion = $WPFCMTBImageVer.Text CMDescription = $WPFCMTBDescription.Text CMBinDifRep = $WPFCMCBBinDirRep.IsChecked CMSiteCode = $WPFCMTBSitecode.Text CMSiteServer = $WPFCMTBSiteServer.Text CMDPGroup = $WPFCMCBDPDPG.SelectedItem CMDPList = $WPFCMLBDPs.Items UpdateSource = $WPFUSCBSelectCatalogSource.SelectedItem UpdateMIS = $WPFMISCBCheckForUpdates.IsChecked AutoFillVersion = $WPFCMCBImageVerAuto.IsChecked AutoFillDesc = $WPFCMCBDescriptionAuto.IsChecked DefaultAppCB = $WPFCustomCBEnableApp.IsChecked DefaultAppPath = $WPFCustomTBDefaultApp.Text StartMenuCB = $WPFCustomCBEnableStart.IsChecked StartMenuPath = $WPFCustomTBStartMenu.Text RegFilesCB = $WPFCustomCBEnableRegistry.IsChecked RegFilesLB = $WPFCustomLBRegistry.Items SUOptional = $WPFUpdatesCBEnableOptional.IsChecked SUDynamic = $WPFUpdatesCBEnableDynamic.IsChecked ApplyDynamicCB = $WPFMISCBDynamicUpdates.IsChecked UpdateBootCB = $WPFMISCBBootWIM.IsChecked DoNotCreateWIMCB = $WPFMISCBNoWIM.IsChecked CreateISO = $WPFMISCBISO.IsChecked ISOFileName = $WPFMISTBISOFileName.Text ISOFilePath = $WPFMISTBFilePath.Text UpgradePackageCB = $WPFMISCBUpgradePackage.IsChecked UpgradePackPath = $WPFMISTBUpgradePackage.Text IncludeOptionCB = $WPFUpdatesOptionalEnableCheckBox.IsChecked SourceVersion = $WPFSourceWimTBVersionNum.text } if ($CM -eq $False) { Update-Log -data "Saving configuration file $filename" -Class Information try { $CurrentConfig | Export-Clixml -Path $global:workdir\Configs\$filename -ErrorAction Stop Update-Log -data 'file saved' -Class Information } catch { Update-Log -data "Couldn't save file" -Class Error } } else { Update-Log -data "Saving ConfigMgr Image info for Package $filename" -Class Information $CurrentConfig.CMPackageID = $filename $CurrentConfig.CMImageType = 'Update Existing Image' $CurrentConfig.CMImageType if ((Test-Path -Path $global:workdir\ConfigMgr\PackageInfo) -eq $False) { Update-Log -Data 'Creating ConfigMgr Package Info folder...' -Class Information try { New-Item -ItemType Directory -Path $global:workdir\ConfigMgr\PackageInfo -ErrorAction Stop } catch { Update-Log -Data "Couldn't create the folder. Likely a permission issue" -Class Error } } try { $CurrentConfig | Export-Clixml -Path $global:workdir\ConfigMgr\PackageInfo\$filename -Force -ErrorAction Stop Update-Log -data 'file saved' -Class Information } catch { Update-Log -data "Couldn't save file" -Class Error } } } #Function to import configurations from file Function Get-Configuration($filename) { Update-Log -data "Importing config from $filename" -Class Information try { $settings = Import-Clixml -Path $filename -ErrorAction Stop Update-Log -data 'Config file read...' -Class Information $WPFSourceWIMSelectWIMTextBox.text = $settings.SourcePath $WPFSourceWimIndexTextBox.text = $settings.SourceIndex $WPFSourceWIMImgDesTextBox.text = $settings.SourceEdition $WPFUpdatesEnableCheckBox.IsChecked = $settings.UpdatesEnabled $WPFJSONEnableCheckBox.IsChecked = $settings.AutopilotEnabled $WPFJSONTextBox.text = $settings.AutopilotPath $WPFDriverCheckBox.IsChecked = $settings.DriversEnabled $WPFDriverDir1TextBox.text = $settings.DriverPath1 $WPFDriverDir2TextBox.text = $settings.DriverPath2 $WPFDriverDir3TextBox.text = $settings.DriverPath3 $WPFDriverDir4TextBox.text = $settings.DriverPath4 $WPFDriverDir5TextBox.text = $settings.DriverPath5 $WPFAppxCheckBox.IsChecked = $settings.AppxIsEnabled $WPFAppxTextBox.text = $settings.AppxSelected -split ' ' $WPFMISWimNameTextBox.text = $settings.WIMName $WPFMISWimFolderTextBox.text = $settings.WIMPath $WPFMISMountTextBox.text = $settings.MountPath $global:SelectedAppx = $settings.AppxSelected -split ' ' $WPFMISDotNetCheckBox.IsChecked = $settings.DotNetEnabled $WPFMISOneDriveCheckBox.IsChecked = $settings.OneDriveEnabled $WPFCustomCBLangPacks.IsChecked = $settings.LPsEnabled $WPFCustomCBLEP.IsChecked = $settings.LXPsEnabled $WPFCustomCBFOD.IsChecked = $settings.FODsEnabled $WPFMISCBPauseMount.IsChecked = $settings.PauseAfterMount $WPFMISCBPauseDismount.IsChecked = $settings.PauseBeforeDM $WPFCustomCBRunScript.IsChecked = $settings.RunScript $WPFCustomCBScriptTiming.SelectedItem = $settings.ScriptTiming $WPFCustomTBFile.Text = $settings.ScriptFile $WPFCustomTBParameters.Text = $settings.ScriptParams $WPFCMCBImageType.SelectedItem = $settings.CMImageType $WPFCMTBPackageID.Text = $settings.CMPackageID $WPFCMTBImageName.Text = $settings.CMImageName $WPFCMTBImageVer.Text = $settings.CMVersion $WPFCMTBDescription.Text = $settings.CMDescription $WPFCMCBBinDirRep.IsChecked = $settings.CMBinDifRep $WPFCMTBSitecode.Text = $settings.CMSiteCode $WPFCMTBSiteServer.Text = $settings.CMSiteServer $WPFCMCBDPDPG.SelectedItem = $settings.CMDPGroup $WPFUSCBSelectCatalogSource.SelectedItem = $settings.UpdateSource $WPFMISCBCheckForUpdates.IsChecked = $settings.UpdateMIS $WPFCMCBImageVerAuto.IsChecked = $settings.AutoFillVersion $WPFCMCBDescriptionAuto.IsChecked = $settings.AutoFillDesc $WPFCustomCBEnableApp.IsChecked = $settings.DefaultAppCB $WPFCustomTBDefaultApp.Text = $settings.DefaultAppPath $WPFCustomCBEnableStart.IsChecked = $settings.StartMenuCB $WPFCustomTBStartMenu.Text = $settings.StartMenuPath $WPFCustomCBEnableRegistry.IsChecked = $settings.RegFilesCB $WPFUpdatesCBEnableOptional.IsChecked = $settings.SUOptional $WPFUpdatesCBEnableDynamic.IsChecked = $settings.SUDynamic $WPFMISCBDynamicUpdates.IsChecked = $settings.ApplyDynamicCB $WPFMISCBBootWIM.IsChecked = $settings.UpdateBootCB $WPFMISCBNoWIM.IsChecked = $settings.DoNotCreateWIMCB $WPFMISCBISO.IsChecked = $settings.CreateISO $WPFMISTBISOFileName.Text = $settings.ISOFileName $WPFMISTBFilePath.Text = $settings.ISOFilePath $WPFMISCBUpgradePackage.IsChecked = $settings.UpgradePackageCB $WPFMISTBUpgradePackage.Text = $settings.UpgradePackPath $WPFUpdatesOptionalEnableCheckBox.IsChecked = $settings.IncludeOptionCB $WPFSourceWimTBVersionNum.text = $settings.SourceVersion $LEPs = $settings.LPListBox $LXPs = $settings.LXPListBox $FODs = $settings.FODListBox $DPs = $settings.CMDPList $REGs = $settings.RegFilesLB Update-Log -data 'Configration set' -class Information Update-Log -data 'Clearing list boxes...' -Class Information $WPFCustomLBLangPacks.Items.Clear() $WPFCustomLBLEP.Items.Clear() $WPFCustomLBFOD.Items.Clear() $WPFCMLBDPs.Items.Clear() $WPFCustomLBRegistry.Items.Clear() Update-Log -data 'Populating list boxes...' -class Information foreach ($LEP in $LEPs) { $WPFCustomLBLangPacks.Items.Add($LEP) | Out-Null } foreach ($LXP in $LXPs) { $WPFCustomLBLEP.Items.Add($LXP) | Out-Null } foreach ($FOD in $FODs) { $WPFCustomLBFOD.Items.Add($FOD) | Out-Null } foreach ($DP in $DPs) { $WPFCMLBDPs.Items.Add($DP) | Out-Null } foreach ($REG in $REGs) { $WPFCustomLBRegistry.Items.Add($REG) | Out-Null } Import-WimInfo -IndexNumber $WPFSourceWimIndexTextBox.text -SkipUserConfirmation if ($WPFJSONEnableCheckBox.IsChecked -eq $true) { Invoke-ParseJSON -file $WPFJSONTextBox.text } if ($WPFCMCBImageType.SelectedItem -eq 'Update Existing Image') { Get-ImageInfo -PackID $settings.CMPackageID } Reset-MISCheckBox } catch { Update-Log -data "Could not import from $filename" -Class Error } Invoke-CheckboxCleanup Update-Log -data 'Config file loaded successfully' -Class Information } #Function to select configuration file Function Select-Config { $SourceXML = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = "$global:workdir\Configs" Filter = 'XML (*.XML)|' } $null = $SourceXML.ShowDialog() $WPFSLLoadTextBox.text = $SourceXML.FileName Get-Configuration -filename $WPFSLLoadTextBox.text } #Function to reset reminder values from check boxes on the MIS tab when loading a config Function Reset-MISCheckBox { Update-Log -data 'Refreshing MIS Values...' -class Information If ($WPFJSONEnableCheckBox.IsChecked -eq $true) { $WPFJSONButton.IsEnabled = $True $WPFMISJSONTextBox.Text = 'True' } If ($WPFDriverCheckBox.IsChecked -eq $true) { $WPFDriverDir1Button.IsEnabled = $True $WPFDriverDir2Button.IsEnabled = $True $WPFDriverDir3Button.IsEnabled = $True $WPFDriverDir4Button.IsEnabled = $True $WPFDriverDir5Button.IsEnabled = $True $WPFMISDriverTextBox.Text = 'True' } If ($WPFUpdatesEnableCheckBox.IsChecked -eq $true) { $WPFMISUpdatesTextBox.Text = 'True' } If ($WPFAppxCheckBox.IsChecked -eq $true) { $WPFAppxButton.IsEnabled = $True $WPFMISAppxTextBox.Text = 'True' } If ($WPFCustomCBEnableApp.IsChecked -eq $true) { $WPFCustomBDefaultApp.IsEnabled = $True } If ($WPFCustomCBEnableStart.IsChecked -eq $true) { $WPFCustomBStartMenu.IsEnabled = $True } If ($WPFCustomCBEnableRegistry.IsChecked -eq $true) { $WPFCustomBRegistryAdd.IsEnabled = $True $WPFCustomBRegistryRemove.IsEnabled = $True } } #Function to run WIM Witch from a config file Function Invoke-RunConfigFile($filename) { Update-Log -Data "Loading the config file: $filename" -Class Information Get-Configuration -filename $filename Update-Log -Data $WWScriptVer Invoke-MakeItSo -appx $global:SelectedAppx Write-Output ' ' Write-Output '##########################################################' Write-Output ' ' } function Show-ClosingText { #Before you start bitching about write-host, write-output doesn't work with the exiting function. Suggestions are welcome. Write-Host ' ' Write-Host '##########################################################' Write-Host ' ' Write-Host 'Thank you for using WIMWitchFK.' Write-Host ' ' Write-Host '##########################################################' } function Show-OpeningText { Clear-Host Write-Output '##########################################################' Write-Output ' ' Write-Output ' ***** Starting WIM Witch *****' Write-Output " version $WWScriptVer" Write-Output ' ' Write-Output '##########################################################' Write-Output ' ' } #Function to check suitability of the proposed mount point folder Function Test-MountPath { param( [parameter(mandatory = $true, HelpMessage = 'mount path')] $path, [parameter(mandatory = $false, HelpMessage = 'clear out the crapola')] [ValidateSet($true)] $clean ) $IsMountPoint = $null $HasFiles = $null $currentmounts = Get-WindowsImage -Mounted foreach ($currentmount in $currentmounts) { if ($currentmount.path -eq $path) { $IsMountPoint = $true } } if ($null -eq $IsMountPoint) { if ( (Get-ChildItem $path | Measure-Object).Count -gt 0) { $HasFiles = $true } } if ($HasFiles -eq $true) { Update-Log -Data 'Folder is not empty' -Class Warning if ($clean -eq $true) { try { Update-Log -Data 'Cleaning folder...' -Class Warning Remove-Item -Path $path\* -Recurse -Force -ErrorAction Stop Update-Log -Data "$path cleared" -Class Warning } catch { Update-Log -Data "Couldn't delete contents of $path" -Class Error Update-Log -Data 'Select a different folder to continue.' -Class Error return } } } if ($IsMountPoint -eq $true) { Update-Log -Data "$path is currently a mount point" -Class Warning if (($IsMountPoint -eq $true) -and ($clean -eq $true)) { try { Update-Log -Data 'Attempting to dismount image from mount point' -Class Warning Dismount-WindowsImage -Path $path -Discard | Out-Null -ErrorAction Stop $IsMountPoint = $null Update-Log -Data 'Dismounting was successful' -Class Warning } catch { Update-Log -Data "Couldn't completely dismount the folder. Ensure" -Class Error Update-Log -data 'all connections to the path are closed, then try again' -Class Error return } } } if (($null -eq $IsMountPoint) -and ($null -eq $HasFiles)) { Update-Log -Data "$path is suitable for mounting" -Class Information } } #Function to check the name of the target file and remediate if necessary Function Test-Name { Param( [parameter(mandatory = $false, HelpMessage = 'what to do')] [ValidateSet('stop', 'append', 'backup', 'overwrite')] $conflict = 'stop' ) If ($WPFMISWimNameTextBox.Text -like '*.wim') { #$WPFLogging.Focus() #Update-Log -Data "New WIM name is valid" -Class Information } If ($WPFMISWimNameTextBox.Text -notlike '*.wim') { $WPFMISWimNameTextBox.Text = $WPFMISWimNameTextBox.Text + '.wim' Update-Log -Data 'Appending new file name with an extension' -Class Information } $WIMpath = $WPFMISWimFolderTextBox.text + '\' + $WPFMISWimNameTextBox.Text $FileCheck = Test-Path -Path $WIMpath #append,overwrite,stop if ($FileCheck -eq $false) { Update-Log -data 'Target WIM file name not in use. Continuing...' -class Information } else { if ($conflict -eq 'append') { $renamestatus = (Rename-Name -file $WIMpath -extension '.wim') if ($renamestatus -eq 'stop') { return 'stop' } } if ($conflict -eq 'overwrite') { Write-Host 'overwrite action' return } if ($conflict -eq 'stop') { $string = $WPFMISWimNameTextBox.Text + ' already exists. Rename the target WIM and try again' Update-Log -Data $string -Class Warning return 'stop' } } Update-Log -Data 'New WIM name is valid' -Class Information } #Function to rename existing target wim file if the target WIM name already exists Function Rename-Name($file, $extension) { $text = 'Renaming existing ' + $extension + ' file...' Update-Log -Data $text -Class Warning $filename = (Split-Path -Leaf $file) $dateinfo = (Get-Item -Path $file).LastWriteTime -replace (' ', '_') -replace ('/', '_') -replace (':', '_') $filename = $filename -replace ($extension, '') $filename = $filename + $dateinfo + $extension try { Rename-Item -Path $file -NewName $filename -ErrorAction Stop $text = $file + ' has been renamed to ' + $filename Update-Log -Data $text -Class Warning } catch { Update-Log -data "Couldn't rename file. Stopping..." -force -Class Error return 'stop' } } #Function to see if the folder WIM Witch was started in is an installation folder. If not, prompt for installation Function Test-WorkingDirectory { $subfolders = @( 'CompletedWIMs' 'Configs' 'drivers' 'jobs' 'logging' 'Mount' 'Staging' 'updates' 'imports' 'imports\WIM' 'imports\DotNet' 'Autopilot' 'backup' ) $count = $null Set-Location -Path $global:workdir Write-Output "WIMWitchFK working directory selected: $global:workdir" Write-Output 'Checking working directory for required folders...' foreach ($subfolder in $subfolders) { if ((Test-Path -Path .\$subfolder) -eq $true) { $count = $count + 1 } } if ($null -eq $count) { Write-Output 'Creating missing folders...' foreach ($subfolder in $subfolders) { if ((Test-Path -Path "$subfolder") -eq $false) { New-Item -Path $subfolder -ItemType Directory | Out-Null Write-Output "Created folder: $subfolder" } } } if ($null -ne $count) { Write-Output 'Creating missing folders...' foreach ($subfolder in $subfolders) { if ((Test-Path -Path "$subfolder") -eq $false) { New-Item -Path $subfolder -ItemType Directory | Out-Null Write-Output "Created folder: $subfolder" } } Write-Output 'Preflight complete. Starting WIM Witch' } } Function Select-WorkingDirectory { $selectWorkingDirectory = New-Object System.Windows.Forms.FolderBrowserDialog $selectWorkingDirectory.Description = 'Select the working directory.' $null = $selectWorkingDirectory.ShowDialog() if ($selectWorkingDirectory.SelectedPath -eq '') { Write-Output 'User Cancelled or invalid entry' exit 0 } return $selectWorkingDirectory.SelectedPath } Function Set-Version($wimversion) { if (($wimversion -eq '10.0.22621.2428') -or ($wimversion -like '10.0.22631.*')) { $version = '23H2' } elseif ($wimversion -like '10.0.16299.*') { $version = '1709' } elseif ($wimversion -like '10.0.17134.*') { $version = '1803' } elseif ($wimversion -like '10.0.17763.*') { $version = '1809' } elseif ($wimversion -like '10.0.18362.*') { $version = '1909' } elseif ($wimversion -like '10.0.14393.*') { $version = '1607' } elseif ($wimversion -like '10.0.19041.*') { $version = '2004' } elseif ($wimversion -like '10.0.22000.*') { $version = '21H2' } elseif ($wimversion -like '10.0.20348.*') { $version = '21H2' } elseif ($wimversion -like '10.0.22621.*') { $version = '22H2' } else { $version = 'Unknown' } return $version } #Function Import-ISO($file, $type, $newname) { Function Import-ISO { $newname = $WPFImportNewNameTextBox.Text $file = $WPFImportISOTextBox.Text #Check to see if destination WIM already exists if ($WPFImportWIMCheckBox.IsChecked -eq $true) { Update-Log -data 'Checking to see if the destination WIM file exists...' -Class Information #check to see if the new name for the imported WIM is valid if (($WPFImportNewNameTextBox.Text -eq '') -or ($WPFImportNewNameTextBox.Text -eq 'Name for the imported WIM')) { Update-Log -Data 'Enter a valid file name for the imported WIM and then try again' -Class Error return } If ($newname -notlike '*.wim') { $newname = $newname + '.wim' Update-Log -Data 'Appending new file name with an extension' -Class Information } if ((Test-Path -Path $global:workdir\Imports\WIM\$newname) -eq $true) { Update-Log -Data 'Destination WIM name already exists. Provide a new name and try again.' -Class Error return } else { Update-Log -Data 'Name appears to be good. Continuing...' -Class Information } } #Mount ISO Update-Log -Data 'Mounting ISO...' -Class Information try { $isomount = Mount-DiskImage -ImagePath $file -PassThru -NoDriveLetter -ErrorAction Stop $iso = $isomount.devicepath } catch { Update-Log -Data 'Could not mount the ISO! Stopping actions...' -Class Error return } if (-not(Test-Path -Path (Join-Path $iso '\sources\'))) { Update-Log -Data 'Could not access the mounted ISO! Stopping actions...' -Class Error try { Invoke-RemoveISOMount -inputObject $isomount } catch { Update-Log -Data 'Attempted to dismount iso - might have failed...' -Class Warning } return } Update-Log -Data "$isomount" -Class Information #Testing for ESD or WIM format if (Test-Path -Path (Join-Path $iso '\sources\install.wim')) { $installWimFound = $true } elseif (Test-Path -Path (Join-Path $iso '\sources\install.esd')) { $installEsdFound = $true Update-Log -data 'Found ESD type installer - attempting to convert to WIM.' -Class Information } else { Update-Log -data 'Error accessing install.wim or install.esd! Breaking' -Class Warning try { Invoke-RemoveISOMount -inputObject $isomount } catch { Update-Log -Data 'Attempted to dismount iso - might have failed...' -Class Warning } return } try { if ($installWimFound) { $windowsver = Get-WindowsImage -ImagePath (Join-Path $iso '\sources\install.wim') -Index 1 -ErrorAction Stop } elseif ($installEsdFound) { $windowsver = Get-WindowsImage -ImagePath (Join-Path $iso '\sources\install.esd') -Index 1 -ErrorAction Stop } ##################### #Right here $version = Set-Version -wimversion $windowsver.version if ($version -eq 2004) { $global:Win10VerDet = $null Invoke-19041Select if ($null -eq $global:Win10VerDet) { Write-Host 'cancelling' return } else { $version = $global:Win10VerDet $global:Win10VerDet = $null } if ($version -eq '20H2') { $version = '2009' } Write-Host $version } } catch { Update-Log -data 'install.wim could not be found or accessed! Skipping...' -Class Warning $installWimFound = $false } #Copy out WIM file #if (($type -eq "all") -or ($type -eq "wim")) { if (($WPFImportWIMCheckBox.IsChecked -eq $true) -and (($installWimFound) -or ($installEsdFound))) { #Copy out the WIM file from the selected ISO try { Update-Log -data 'Purging staging folder...' -Class Information Remove-Item -Path $global:workdir\staging\*.* -Force Update-Log -data 'Purge complete.' -Class Information if ($installWimFound) { Update-Log -Data 'Copying WIM file to the staging folder...' -Class Information Copy-Item -Path $iso\sources\install.wim -Destination $global:workdir\staging -Force -ErrorAction Stop -PassThru } } catch { Update-Log -data "Couldn't copy from the source" -Class Error Invoke-RemoveISOMount -inputObject $isomount return } #convert the ESD file to WIM if ($installEsdFound) { $sourceEsdFile = (Join-Path $iso '\sources\install.esd') Update-Log -Data 'Assessing install.esd file...' -Class Information $indexesFound = Get-WindowsImage -ImagePath $sourceEsdFile Update-Log -Data "$($indexesFound.Count) indexes found for conversion..." -Class Information foreach ($index in $indexesFound) { try { Update-Log -Data "Converting index $($index.ImageIndex) - $($index.ImageName)" -Class Information Export-WindowsImage -SourceImagePath $sourceEsdFile -SourceIndex $($index.ImageIndex) -DestinationImagePath (Join-Path $global:workdir '\staging\install.wim') -CompressionType fast -ErrorAction Stop } catch { Update-Log -Data "Converting index $($index.ImageIndex) failed - skipping..." -Class Error continue } } } #Change file attribute to normal Update-Log -Data 'Setting file attribute of install.wim to Normal' -Class Information $attrib = Get-Item $global:workdir\staging\install.wim $attrib.Attributes = 'Normal' #Rename install.wim to the new name try { $text = 'Renaming install.wim to ' + $newname Update-Log -Data $text -Class Information Rename-Item -Path $global:workdir\Staging\install.wim -NewName $newname -ErrorAction Stop } catch { Update-Log -data "Couldn't rename the copied file. Most likely a weird permissions issues." -Class Error Invoke-RemoveISOMount -inputObject $isomount return } #Move the imported WIM to the imports folder try { Update-Log -data "Moving $newname to imports folder..." -Class Information Move-Item -Path $global:workdir\Staging\$newname -Destination $global:workdir\Imports\WIM -ErrorAction Stop } catch { Update-Log -Data "Couldn't move the new WIM to the staging folder." -Class Error Invoke-RemoveISOMount -inputObject $isomount return } Update-Log -data 'WIM importation complete' -Class Information } #Copy DotNet binaries if ($WPFImportDotNetCheckBox.IsChecked -eq $true) { If (($windowsver.imagename -like '*Windows 10*') -or (($windowsver.imagename -like '*server') -and ($windowsver.version -lt 10.0.20248.0))) { $Path = "$global:workdir\Imports\DotNet\$version" } If (($windowsver.Imagename -like '*server*') -and ($windowsver.version -gt 10.0.20348.0)) { $Path = "$global:workdir\Imports\Dotnet\Windows Server\$version" } If ($windowsver.imagename -like '*Windows 11*') { $Path = "$global:workdir\Imports\Dotnet\Windows 11\$version" } if ((Test-Path -Path $Path) -eq $false) { try { Update-Log -Data 'Creating folders...' -Class Warning New-Item -Path (Split-Path -Path $path -Parent) -Name $version -ItemType Directory -ErrorAction stop | Out-Null } catch { Update-Log -Data "Couldn't creating new folder in DotNet imports folder" -Class Error return } } try { Update-Log -Data 'Copying .Net binaries...' -Class Information Copy-Item -Path $iso\sources\sxs\*netfx3* -Destination $path -Force -ErrorAction Stop } catch { Update-Log -Data "Couldn't copy the .Net binaries" -Class Error return } } #Copy out ISO files if ($WPFImportISOCheckBox.IsChecked -eq $true) { #Determine if is Windows 10 or Windows Server Update-Log -Data 'Importing ISO/Upgrade Package files...' -Class Information if ($windowsver.ImageName -like 'Windows 10*') { $OS = 'Windows 10' } if ($windowsver.ImageName -like 'Windows 11*') { $OS = 'Windows 11' } if ($windowsver.ImageName -like '*Server*') { $OS = 'Windows Server' } Update-Log -Data "$OS detected" -Class Information if ((Test-Path -Path $global:workdir\imports\iso\$OS\$Version) -eq $false) { Update-Log -Data 'Path does not exist. Creating...' -Class Information New-Item -Path $global:workdir\imports\iso\$OS\ -Name $version -ItemType Directory } Update-Log -Data 'Copying boot folder...' -Class Information Copy-Item -Path $iso\boot\ -Destination $global:workdir\imports\iso\$OS\$Version\boot -Recurse -Force #-Exclude install.wim Update-Log -Data 'Copying efi folder...' -Class Information Copy-Item -Path $iso\efi\ -Destination $global:workdir\imports\iso\$OS\$Version\efi -Recurse -Force #-Exclude install.wim Update-Log -Data 'Copying sources folder...' -Class Information Copy-Item -Path $iso\sources\ -Destination $global:workdir\imports\iso\$OS\$Version\sources -Recurse -Force -Exclude install.wim Update-Log -Data 'Copying support folder...' -Class Information Copy-Item -Path $iso\support\ -Destination $global:workdir\imports\iso\$OS\$Version\support -Recurse -Force #-Exclude install.wim Update-Log -Data 'Copying files in root folder...' -Class Information Copy-Item $iso\autorun.inf -Destination $global:workdir\imports\iso\$OS\$Version\ -Force Copy-Item $iso\bootmgr -Destination $global:workdir\imports\iso\$OS\$Version\ -Force Copy-Item $iso\bootmgr.efi -Destination $global:workdir\imports\iso\$OS\$Version\ -Force Copy-Item $iso\setup.exe -Destination $global:workdir\imports\iso\$OS\$Version\ -Force } #Dismount and finish try { Update-Log -Data 'Dismount!' -Class Information Invoke-RemoveISOMount -inputObject $isomount } catch { Update-Log -Data "Couldn't dismount the ISO. WIM Witch uses a file mount option that does not" -Class Error Update-Log -Data 'provision a drive letter. Use the Dismount-DiskImage command to manaully dismount.' -Class Error } Update-Log -data 'Importing complete' -class Information } #Function to select ISO for import Function Select-ISO { $SourceISO = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Filter = 'ISO (*.iso)|' } $null = $SourceISO.ShowDialog() $WPFImportISOTextBox.text = $SourceISO.FileName if ($SourceISO.FileName -notlike '*.iso') { Update-Log -Data 'An ISO file not selected. Please select a valid file to continue.' -Class Warning return } $text = $WPFImportISOTextBox.text + ' selected as the ISO to import from' Update-Log -Data $text -class Information } #Function to inject the .Net 3.5 binaries from the import folder Function Add-DotNet { $buildnum = Get-WinVersionNumber $OSType = Get-WindowsType #fix the build number 21h if ($OSType -eq 'Windows 10') { $DotNetFiles = "$global:workdir\imports\DotNet\$buildnum" } if (($OSType -eq 'Windows 11') -or ($OSType -eq 'Windows Server')) { $DotNetFiles = "$global:workdir\imports\DotNet\$OSType\$buildnum" } try { $text = 'Injecting .Net 3.5 binaries from ' + $DotNetFiles Update-Log -Data $text -Class Information Add-WindowsPackage -PackagePath $DotNetFiles -Path $WPFMISMountTextBox.Text -ErrorAction Continue | Out-Null } catch { Update-Log -Data "Couldn't inject .Net Binaries" -Class Warning Update-Log -data $_.Exception.Message -Class Error return } Update-Log -Data '.Net 3.5 injection complete' -Class Information } #Function to see if the .Net binaries for the select Win10 version exist Function Test-DotNetExists { $OSType = Get-WindowsType #$buildnum = Get-WinVersionNumber $buildnum = $WPFSourceWimTBVersionNum.text if ($OSType -eq 'Windows 10') { if ($buildnum -eq '20H2') { $Buildnum = '2009' } $DotNetFiles = "$global:workdir\imports\DotNet\$buildnum" } if (($OSType -eq 'Windows 11') -or ($OSType -eq 'Windows Server')) { $DotNetFiles = "$global:workdir\imports\DotNet\$OSType\$buildnum" } Test-Path -Path $DotNetFiles\* if ((Test-Path -Path $DotNetFiles\*) -eq $false) { $text = '.Net 3.5 Binaries are not present for ' + $buildnum Update-Log -Data $text -Class Warning Update-Log -data 'Import .Net from an ISO or disable injection to continue' -Class Warning return $false } } #For those who like to dig through code and find notes from the dev team: #Each Function is listed in the order it was created. This point marks #where v1.0 was released. Everything #below is from updates -DRR 10/22/2020 Function Install-WimWitchUpgrade { Write-Output 'Would you like to upgrade WIM Witch?' $yesno = Read-Host -Prompt '(Y/N)' Write-Output $yesno if (($yesno -ne 'Y') -and ($yesno -ne 'N')) { Write-Output 'Invalid entry, try again.' Install-WimWitchUpgrade } if ($yesno -eq 'y') { Backup-WIMWitch try { Save-Script -Name 'WIMWitch' -Path $global:workdir -Force -ErrorAction Stop Write-Output 'New version has been applied. WIM Witch will now exit.' Write-Output 'Please restart WIM Witch' exit } catch { Write-Output "Couldn't upgrade. Try again when teh tubes are clear" return } } if ($yesno -eq 'n') { Write-Output "You'll want to upgrade at some point." Update-Log -Data 'Upgrade to new version was declined' -Class Warning Update-Log -Data 'Continuing to start WIM Witch...' -Class Warning } } #Function to backup WIM Witch script file during upgrade Function Backup-WIMWitch { Update-log -data 'Backing up existing WIM Witch script...' -Class Information $scriptname = Split-Path $MyInvocation.PSCommandPath -Leaf #Find local script name Update-Log -data 'The script to be backed up is: ' -Class Information Update-Log -data $MyInvocation.PSCommandPath -Class Information try { Update-Log -data 'Copy script to backup folder...' -Class Information Copy-Item -Path $scriptname -Destination $global:workdir\backup -ErrorAction Stop Update-Log -Data 'Successfully copied...' -Class Information } catch { Update-Log -data "Couldn't copy the WIM Witch script. My guess is a permissions issue" -Class Error Update-Log -Data 'Exiting out of an over abundance of caution' -Class Error exit } try { Update-Log -data 'Renaming archived script...' -Class Information Rename-Name -file $global:workdir\backup\$scriptname -extension '.ps1' Update-Log -data 'Backup successfully renamed for archiving' -class Information } catch { Update-Log -Data "Backed-up script couldn't be renamed. This isn't a critical error" -Class Warning Update-Log -Data "You may want to change it's name so it doesn't get overwritten." -Class Warning Update-Log -Data 'Continuing with WIM Witch upgrade...' -Class Warning } } #Function to download current OneDrive client #Most of this was stolen from David Segura @SeguraOSD Function Get-OneDrive { #https://go.microsoft.com/fwlink/p/?LinkID=844652 -Possible new link location. #https://go.microsoft.com/fwlink/?linkid=2181064 - x64 installer Update-Log -Data 'Downloading latest 32-bit OneDrive agent installer...' -class Information $DownloadUrl = 'https://go.microsoft.com/fwlink/p/?LinkId=248256' $DownloadPath = "$global:workdir\updates\OneDrive" $DownloadFile = 'OneDriveSetup.exe' if (!(Test-Path "$DownloadPath")) { New-Item -Path $DownloadPath -ItemType Directory -Force | Out-Null } Invoke-WebRequest -Uri $DownloadUrl -OutFile "$DownloadPath\$DownloadFile" if (Test-Path "$DownloadPath\$DownloadFile") { Update-Log -Data 'OneDrive Download Complete' -Class Information } else { Update-log -Data 'OneDrive could not be downloaded' -Class Error } Update-Log -Data 'Downloading latest 64-bit OneDrive agent installer...' -class Information $DownloadUrl = 'https://go.microsoft.com/fwlink/?linkid=2181064' $DownloadPath = "$global:workdir\updates\OneDrive\x64" $DownloadFile = 'OneDriveSetup.exe' if (!(Test-Path "$DownloadPath")) { New-Item -Path $DownloadPath -ItemType Directory -Force | Out-Null } Invoke-WebRequest -Uri $DownloadUrl -OutFile "$DownloadPath\$DownloadFile" if (Test-Path "$DownloadPath\$DownloadFile") { Update-Log -Data 'OneDrive Download Complete' -Class Information } else { Update-log -Data 'OneDrive could not be downloaded' -Class Error } } #Function to copy new OneDrive client installer to mount path Function Copy-OneDrive { Update-Log -data 'Updating OneDrive x86 client' -class information try { Update-Log -Data 'Setting ACL on the original OneDriveSetup.exe file' -Class Information $mountpath = $WPFMISMountTextBox.text $AclBAK = Get-Acl "$mountpath\Windows\SysWOW64\OneDriveSetup.exe" $user = $env:USERDOMAIN + '\' + $env:USERNAME $Account = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $user $item = Get-Item "$mountpath\Windows\SysWOW64\OneDriveSetup.exe" $Acl = $null # Reset the $Acl variable to $null $Acl = Get-Acl -Path $Item.FullName # Get the ACL from the item $Acl.SetOwner($Account) # Update the in-memory ACL Set-Acl -Path $Item.FullName -AclObject $Acl -ErrorAction Stop # Set the updated ACL on the target item Update-Log -Data 'Ownership of OneDriveSetup.exe siezed' -Class Information $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($user, 'FullControl', 'Allow') $Acl.SetAccessRule($Ar) Set-Acl "$mountpath\Windows\SysWOW64\OneDriveSetup.exe" $Acl -ErrorAction Stop | Out-Null Update-Log -Data 'ACL successfully updated. Continuing...' } catch { Update-Log -data "Couldn't set the ACL on the original file" -Class Error return } try { Update-Log -data 'Copying updated OneDrive agent installer...' -Class Information Copy-Item "$global:workdir\updates\OneDrive\OneDriveSetup.exe" -Destination "$mountpath\Windows\SysWOW64" -Force -ErrorAction Stop Update-Log -Data 'OneDrive installer successfully copied.' -Class Information } catch { Update-Log -data "Couldn't copy the OneDrive installer file." -class Error Update-Log -data $_.Exception.Message -Class Error return } try { Update-Log -data 'Restoring original ACL to OneDrive installer.' -Class Information Set-Acl "$mountpath\Windows\SysWOW64\OneDriveSetup.exe" $AclBAK -ErrorAction Stop | Out-Null Update-Log -data 'Restoration complete' -Class Information } catch { Update-Log "Couldn't restore original ACLs. Continuing." -Class Error } } #Function to copy new OneDrive client installer to mount path Function Copy-OneDrivex64 { Update-Log -data 'Updating OneDrive x64 client' -class information try { Update-Log -Data 'Setting ACL on the original OneDriveSetup.exe file' -Class Information $mountpath = $WPFMISMountTextBox.text $AclBAK = Get-Acl "$mountpath\Windows\System32\OneDriveSetup.exe" $user = $env:USERDOMAIN + '\' + $env:USERNAME $Account = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $user $item = Get-Item "$mountpath\Windows\System32\OneDriveSetup.exe" $Acl = $null # Reset the $Acl variable to $null $Acl = Get-Acl -Path $Item.FullName # Get the ACL from the item $Acl.SetOwner($Account) # Update the in-memory ACL Set-Acl -Path $Item.FullName -AclObject $Acl -ErrorAction Stop # Set the updated ACL on the target item Update-Log -Data 'Ownership of OneDriveSetup.exe siezed' -Class Information $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($user, 'FullControl', 'Allow') $Acl.SetAccessRule($Ar) Set-Acl "$mountpath\Windows\System32\OneDriveSetup.exe" $Acl -ErrorAction Stop | Out-Null Update-Log -Data 'ACL successfully updated. Continuing...' } catch { Update-Log -data "Couldn't set the ACL on the original file" -Class Error return } try { Update-Log -data 'Copying updated OneDrive agent installer...' -Class Information Copy-Item "$global:workdir\updates\OneDrive\x64\OneDriveSetup.exe" -Destination "$mountpath\Windows\System32" -Force -ErrorAction Stop Update-Log -Data 'OneDrive installer successfully copied.' -Class Information } catch { Update-Log -data "Couldn't copy the OneDrive installer file." -class Error Update-Log -data $_.Exception.Message -Class Error return } try { Update-Log -data 'Restoring original ACL to OneDrive installer.' -Class Information Set-Acl "$mountpath\Windows\System32\OneDriveSetup.exe" $AclBAK -ErrorAction Stop | Out-Null Update-Log -data 'Restoration complete' -Class Information } catch { Update-Log "Couldn't restore original ACLs. Continuing." -Class Error } } #Function to call the next three Functions. This determines WinOS and WinVer and calls the Function Function Select-LPFODCriteria($Type) { $WinOS = Get-WindowsType #$WinVer = Get-WinVersionNumber $WinVer = $WPFSourceWimTBVersionNum.text if ($WinOS -eq 'Windows 10') { if (($Winver -eq '2009') -or ($winver -eq '20H2') -or ($winver -eq '21H1') -or ($winver -eq '21H2') -or ($winver -eq '22H2')) { $winver = '2004' } } if ($type -eq 'LP') { if ((Test-Path -Path $global:workdir\imports\Lang\$WinOS\$Winver\LanguagePacks) -eq $false) { Update-Log -Data 'Source not found. Please import some language packs and try again' -Class Error return } Select-LanguagePacks -winver $Winver -WinOS $WinOS } If ($type -eq 'LXP') { if ((Test-Path -Path $global:workdir\imports\Lang\$WinOS\$Winver\localexperiencepack) -eq $false) { Update-Log -Data 'Source not found. Please import some Local Experience Packs and try again' -Class Error return } Select-LocalExperiencePack -winver $Winver -WinOS $WinOS } if ($type -eq 'FOD') { if ((Test-Path -Path $global:workdir\imports\FODs\$WinOS\$Winver\) -eq $false) { Update-Log -Data 'Source not found. Please import some Demanding Features and try again' -Class Error return } Select-FeaturesOnDemand -winver $Winver -WinOS $WinOS } } #Function to select langauge packs for injection Function Select-LanguagePacks($winver, $WinOS) { $LPSourceFolder = $global:workdir + '\imports\lang\' + $WinOS + '\' + $winver + '\' + 'LanguagePacks' + '\' $items = (Get-ChildItem -Path $LPSourceFolder | Select-Object -Property Name | Out-GridView -Title 'Select Language Packs' -PassThru) foreach ($item in $items) { $WPFCustomLBLangPacks.Items.Add($item.name) } } #Function to select LXP packs for injection Function Select-LocalExperiencePack($winver, $WinOS) { $LPSourceFolder = $global:workdir + '\imports\lang\' + $WinOS + '\' + $winver + '\' + 'localexperiencepack' + '\' $items = (Get-ChildItem -Path $LPSourceFolder | Select-Object -Property Name | Out-GridView -Title 'Select Local Experience Packs' -PassThru) foreach ($item in $items) { $WPFCustomLBLEP.Items.Add($item.name) } } #Hey Donna #Function to select FODs for injection Function Select-FeaturesOnDemand($winver, $WinOS) { $Win10_1909_FODs = @('Accessibility.Braille~~~~0.0.1.0', 'Analog.Holographic.Desktop~~~~0.0.1.0', 'App.Support.QuickAssist~~~~0.0.1.0', 'Browser.InternetExplorer~~~~0.0.11.0', 'Hello.Face.18330~~~~0.0.1.0', 'Hello.Face.Migration.18330~~~~0.0.1.0', 'Language.Basic~~~af-ZA~0.0.1.0', 'Language.Basic~~~ar-SA~0.0.1.0', 'Language.Basic~~~as-IN~0.0.1.0', 'Language.Basic~~~az-LATN-AZ~0.0.1.0', 'Language.Basic~~~ba-RU~0.0.1.0', 'Language.Basic~~~be-BY~0.0.1.0', 'Language.Basic~~~bg-BG~0.0.1.0', 'Language.Basic~~~bn-BD~0.0.1.0', 'Language.Basic~~~bn-IN~0.0.1.0', 'Language.Basic~~~bs-LATN-BA~0.0.1.0', 'Language.Basic~~~ca-ES~0.0.1.0', 'Language.Basic~~~cs-CZ~0.0.1.0', 'Language.Basic~~~cy-GB~0.0.1.0', 'Language.Basic~~~da-DK~0.0.1.0', 'Language.Basic~~~de-CH~0.0.1.0', 'Language.Basic~~~de-DE~0.0.1.0', 'Language.Basic~~~el-GR~0.0.1.0', 'Language.Basic~~~en-AU~0.0.1.0', 'Language.Basic~~~en-CA~0.0.1.0', 'Language.Basic~~~en-GB~0.0.1.0', 'Language.Basic~~~en-IN~0.0.1.0', 'Language.Basic~~~en-US~0.0.1.0', 'Language.Basic~~~es-ES~0.0.1.0', 'Language.Basic~~~es-MX~0.0.1.0', 'Language.Basic~~~es-US~0.0.1.0', 'Language.Basic~~~et-EE~0.0.1.0', 'Language.Basic~~~eu-ES~0.0.1.0', 'Language.Basic~~~fa-IR~0.0.1.0', 'Language.Basic~~~fi-FI~0.0.1.0', 'Language.Basic~~~fil-PH~0.0.1.0', 'Language.Basic~~~fr-BE~0.0.1.0', 'Language.Basic~~~fr-CA~0.0.1.0', 'Language.Basic~~~fr-CH~0.0.1.0', 'Language.Basic~~~fr-FR~0.0.1.0', 'Language.Basic~~~ga-IE~0.0.1.0', 'Language.Basic~~~gd-GB~0.0.1.0', 'Language.Basic~~~gl-ES~0.0.1.0', 'Language.Basic~~~gu-IN~0.0.1.0', 'Language.Basic~~~ha-LATN-NG~0.0.1.0', 'Language.Basic~~~haw-US~0.0.1.0', 'Language.Basic~~~he-IL~0.0.1.0', 'Language.Basic~~~hi-IN~0.0.1.0', 'Language.Basic~~~hr-HR~0.0.1.0', 'Language.Basic~~~hu-HU~0.0.1.0', 'Language.Basic~~~hy-AM~0.0.1.0', 'Language.Basic~~~id-ID~0.0.1.0', 'Language.Basic~~~ig-NG~0.0.1.0', 'Language.Basic~~~is-IS~0.0.1.0', 'Language.Basic~~~it-IT~0.0.1.0', 'Language.Basic~~~ja-JP~0.0.1.0', 'Language.Basic~~~ka-GE~0.0.1.0', 'Language.Basic~~~kk-KZ~0.0.1.0', 'Language.Basic~~~kl-GL~0.0.1.0', 'Language.Basic~~~kn-IN~0.0.1.0', 'Language.Basic~~~ko-KR~0.0.1.0', 'Language.Basic~~~kok-DEVA-IN~0.0.1.0', 'Language.Basic~~~ky-KG~0.0.1.0', 'Language.Basic~~~lb-LU~0.0.1.0', 'Language.Basic~~~lt-LT~0.0.1.0', 'Language.Basic~~~lv-LV~0.0.1.0', 'Language.Basic~~~mi-NZ~0.0.1.0', 'Language.Basic~~~mk-MK~0.0.1.0', 'Language.Basic~~~ml-IN~0.0.1.0', 'Language.Basic~~~mn-MN~0.0.1.0', 'Language.Basic~~~mr-IN~0.0.1.0', 'Language.Basic~~~ms-BN~0.0.1.0', 'Language.Basic~~~ms-MY~0.0.1.0', 'Language.Basic~~~mt-MT~0.0.1.0', 'Language.Basic~~~nb-NO~0.0.1.0', 'Language.Basic~~~ne-NP~0.0.1.0', 'Language.Basic~~~nl-NL~0.0.1.0', 'Language.Basic~~~nn-NO~0.0.1.0', 'Language.Basic~~~nso-ZA~0.0.1.0', 'Language.Basic~~~or-IN~0.0.1.0', 'Language.Basic~~~pa-IN~0.0.1.0', 'Language.Basic~~~pl-PL~0.0.1.0', 'Language.Basic~~~ps-AF~0.0.1.0', 'Language.Basic~~~pt-BR~0.0.1.0', 'Language.Basic~~~pt-PT~0.0.1.0', 'Language.Basic~~~rm-CH~0.0.1.0', 'Language.Basic~~~ro-RO~0.0.1.0', 'Language.Basic~~~ru-RU~0.0.1.0', 'Language.Basic~~~rw-RW~0.0.1.0', 'Language.Basic~~~sah-RU~0.0.1.0', 'Language.Basic~~~si-LK~0.0.1.0', 'Language.Basic~~~sk-SK~0.0.1.0', 'Language.Basic~~~sl-SI~0.0.1.0', 'Language.Basic~~~sq-AL~0.0.1.0', 'Language.Basic~~~sr-CYRL-RS~0.0.1.0', 'Language.Basic~~~sr-LATN-RS~0.0.1.0', 'Language.Basic~~~sv-SE~0.0.1.0', 'Language.Basic~~~sw-KE~0.0.1.0', 'Language.Basic~~~ta-IN~0.0.1.0', 'Language.Basic~~~te-IN~0.0.1.0', 'Language.Basic~~~tg-CYRL-TJ~0.0.1.0', 'Language.Basic~~~th-TH~0.0.1.0', 'Language.Basic~~~tk-TM~0.0.1.0', 'Language.Basic~~~tn-ZA~0.0.1.0', 'Language.Basic~~~tr-TR~0.0.1.0', 'Language.Basic~~~tt-RU~0.0.1.0', 'Language.Basic~~~ug-CN~0.0.1.0', 'Language.Basic~~~uk-UA~0.0.1.0', 'Language.Basic~~~ur-PK~0.0.1.0', 'Language.Basic~~~uz-LATN-UZ~0.0.1.0', 'Language.Basic~~~vi-VN~0.0.1.0', 'Language.Basic~~~wo-SN~0.0.1.0', 'Language.Basic~~~xh-ZA~0.0.1.0', 'Language.Basic~~~yo-NG~0.0.1.0', 'Language.Basic~~~zh-CN~0.0.1.0', 'Language.Basic~~~zh-HK~0.0.1.0', 'Language.Basic~~~zh-TW~0.0.1.0', 'Language.Basic~~~zu-ZA~0.0.1.0', 'Language.Fonts.Arab~~~und-ARAB~0.0.1.0', 'Language.Fonts.Beng~~~und-BENG~0.0.1.0', 'Language.Fonts.Cans~~~und-CANS~0.0.1.0', 'Language.Fonts.Cher~~~und-CHER~0.0.1.0', 'Language.Fonts.Deva~~~und-DEVA~0.0.1.0', 'Language.Fonts.Ethi~~~und-ETHI~0.0.1.0', 'Language.Fonts.Gujr~~~und-GUJR~0.0.1.0', 'Language.Fonts.Guru~~~und-GURU~0.0.1.0', 'Language.Fonts.Hans~~~und-HANS~0.0.1.0', 'Language.Fonts.Hant~~~und-HANT~0.0.1.0', 'Language.Fonts.Hebr~~~und-HEBR~0.0.1.0', 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0', 'Language.Fonts.Khmr~~~und-KHMR~0.0.1.0', 'Language.Fonts.Knda~~~und-KNDA~0.0.1.0', 'Language.Fonts.Kore~~~und-KORE~0.0.1.0', 'Language.Fonts.Laoo~~~und-LAOO~0.0.1.0', 'Language.Fonts.Mlym~~~und-MLYM~0.0.1.0', 'Language.Fonts.Orya~~~und-ORYA~0.0.1.0', 'Language.Fonts.PanEuropeanSupplementalFonts~~~~0.0.1.0', 'Language.Fonts.Sinh~~~und-SINH~0.0.1.0', 'Language.Fonts.Syrc~~~und-SYRC~0.0.1.0', 'Language.Fonts.Taml~~~und-TAML~0.0.1.0', 'Language.Fonts.Telu~~~und-TELU~0.0.1.0', 'Language.Fonts.Thai~~~und-THAI~0.0.1.0', 'Language.Handwriting~~~af-ZA~0.0.1.0', 'Language.Handwriting~~~bs-LATN-BA~0.0.1.0', 'Language.Handwriting~~~ca-ES~0.0.1.0', 'Language.Handwriting~~~cs-CZ~0.0.1.0', 'Language.Handwriting~~~cy-GB~0.0.1.0', 'Language.Handwriting~~~da-DK~0.0.1.0', 'Language.Handwriting~~~de-DE~0.0.1.0', 'Language.Handwriting~~~el-GR~0.0.1.0', 'Language.Handwriting~~~en-GB~0.0.1.0', 'Language.Handwriting~~~en-US~0.0.1.0', 'Language.Handwriting~~~es-ES~0.0.1.0', 'Language.Handwriting~~~es-MX~0.0.1.0', 'Language.Handwriting~~~eu-ES~0.0.1.0', 'Language.Handwriting~~~fi-FI~0.0.1.0', 'Language.Handwriting~~~fr-FR~0.0.1.0', 'Language.Handwriting~~~ga-IE~0.0.1.0', 'Language.Handwriting~~~gd-GB~0.0.1.0', 'Language.Handwriting~~~gl-ES~0.0.1.0', 'Language.Handwriting~~~hi-IN~0.0.1.0', 'Language.Handwriting~~~hr-HR~0.0.1.0', 'Language.Handwriting~~~id-ID~0.0.1.0', 'Language.Handwriting~~~it-IT~0.0.1.0', 'Language.Handwriting~~~ja-JP~0.0.1.0', 'Language.Handwriting~~~ko-KR~0.0.1.0', 'Language.Handwriting~~~lb-LU~0.0.1.0', 'Language.Handwriting~~~mi-NZ~0.0.1.0', 'Language.Handwriting~~~ms-BN~0.0.1.0', 'Language.Handwriting~~~ms-MY~0.0.1.0', 'Language.Handwriting~~~nb-NO~0.0.1.0', 'Language.Handwriting~~~nl-NL~0.0.1.0', 'Language.Handwriting~~~nn-NO~0.0.1.0', 'Language.Handwriting~~~nso-ZA~0.0.1.0', 'Language.Handwriting~~~pl-PL~0.0.1.0', 'Language.Handwriting~~~pt-BR~0.0.1.0', 'Language.Handwriting~~~pt-PT~0.0.1.0', 'Language.Handwriting~~~rm-CH~0.0.1.0', 'Language.Handwriting~~~ro-RO~0.0.1.0', 'Language.Handwriting~~~ru-RU~0.0.1.0', 'Language.Handwriting~~~rw-RW~0.0.1.0', 'Language.Handwriting~~~sk-SK~0.0.1.0', 'Language.Handwriting~~~sl-SI~0.0.1.0', 'Language.Handwriting~~~sq-AL~0.0.1.0', 'Language.Handwriting~~~sr-CYRL-RS~0.0.1.0', 'Language.Handwriting~~~sr-LATN-RS~0.0.1.0', 'Language.Handwriting~~~sv-SE~0.0.1.0', 'Language.Handwriting~~~sw-KE~0.0.1.0', 'Language.Handwriting~~~tn-ZA~0.0.1.0', 'Language.Handwriting~~~tr-TR~0.0.1.0', 'Language.Handwriting~~~wo-SN~0.0.1.0', 'Language.Handwriting~~~xh-ZA~0.0.1.0', 'Language.Handwriting~~~zh-CN~0.0.1.0', 'Language.Handwriting~~~zh-HK~0.0.1.0', 'Language.Handwriting~~~zh-TW~0.0.1.0', 'Language.Handwriting~~~zu-ZA~0.0.1.0', 'Language.OCR~~~ar-SA~0.0.1.0', 'Language.OCR~~~bg-BG~0.0.1.0', 'Language.OCR~~~bs-LATN-BA~0.0.1.0', 'Language.OCR~~~cs-CZ~0.0.1.0', 'Language.OCR~~~da-DK~0.0.1.0', 'Language.OCR~~~de-DE~0.0.1.0', 'Language.OCR~~~el-GR~0.0.1.0', 'Language.OCR~~~en-GB~0.0.1.0', 'Language.OCR~~~en-US~0.0.1.0', 'Language.OCR~~~es-ES~0.0.1.0', 'Language.OCR~~~es-MX~0.0.1.0', 'Language.OCR~~~fi-FI~0.0.1.0', 'Language.OCR~~~fr-CA~0.0.1.0', 'Language.OCR~~~fr-FR~0.0.1.0', 'Language.OCR~~~hr-HR~0.0.1.0', 'Language.OCR~~~hu-HU~0.0.1.0', 'Language.OCR~~~it-IT~0.0.1.0', 'Language.OCR~~~ja-JP~0.0.1.0', 'Language.OCR~~~ko-KR~0.0.1.0', 'Language.OCR~~~nb-NO~0.0.1.0', 'Language.OCR~~~nl-NL~0.0.1.0', 'Language.OCR~~~pl-PL~0.0.1.0', 'Language.OCR~~~pt-BR~0.0.1.0', 'Language.OCR~~~pt-PT~0.0.1.0', 'Language.OCR~~~ro-RO~0.0.1.0', 'Language.OCR~~~ru-RU~0.0.1.0', 'Language.OCR~~~sk-SK~0.0.1.0', 'Language.OCR~~~sl-SI~0.0.1.0', 'Language.OCR~~~sr-CYRL-RS~0.0.1.0', 'Language.OCR~~~sr-LATN-RS~0.0.1.0', 'Language.OCR~~~sv-SE~0.0.1.0', 'Language.OCR~~~tr-TR~0.0.1.0', 'Language.OCR~~~zh-CN~0.0.1.0', 'Language.OCR~~~zh-HK~0.0.1.0', 'Language.OCR~~~zh-TW~0.0.1.0', 'Language.Speech~~~da-DK~0.0.1.0', 'Language.Speech~~~de-DE~0.0.1.0', 'Language.Speech~~~en-AU~0.0.1.0', 'Language.Speech~~~en-CA~0.0.1.0', 'Language.Speech~~~en-GB~0.0.1.0', 'Language.Speech~~~en-IN~0.0.1.0', 'Language.Speech~~~en-US~0.0.1.0', 'Language.Speech~~~es-ES~0.0.1.0', 'Language.Speech~~~es-MX~0.0.1.0', 'Language.Speech~~~fr-CA~0.0.1.0', 'Language.Speech~~~fr-FR~0.0.1.0', 'Language.Speech~~~it-IT~0.0.1.0', 'Language.Speech~~~ja-JP~0.0.1.0', 'Language.Speech~~~pt-BR~0.0.1.0', 'Language.Speech~~~zh-CN~0.0.1.0', 'Language.Speech~~~zh-HK~0.0.1.0', 'Language.Speech~~~zh-TW~0.0.1.0', 'Language.TextToSpeech~~~ar-EG~0.0.1.0', 'Language.TextToSpeech~~~ar-SA~0.0.1.0', 'Language.TextToSpeech~~~bg-BG~0.0.1.0', 'Language.TextToSpeech~~~ca-ES~0.0.1.0', 'Language.TextToSpeech~~~cs-CZ~0.0.1.0', 'Language.TextToSpeech~~~da-DK~0.0.1.0', 'Language.TextToSpeech~~~de-AT~0.0.1.0', 'Language.TextToSpeech~~~de-CH~0.0.1.0', 'Language.TextToSpeech~~~de-DE~0.0.1.0', 'Language.TextToSpeech~~~el-GR~0.0.1.0', 'Language.TextToSpeech~~~en-AU~0.0.1.0', 'Language.TextToSpeech~~~en-CA~0.0.1.0', 'Language.TextToSpeech~~~en-GB~0.0.1.0', 'Language.TextToSpeech~~~en-IE~0.0.1.0', 'Language.TextToSpeech~~~en-IN~0.0.1.0', 'Language.TextToSpeech~~~en-US~0.0.1.0', 'Language.TextToSpeech~~~es-ES~0.0.1.0', 'Language.TextToSpeech~~~es-MX~0.0.1.0', 'Language.TextToSpeech~~~fi-FI~0.0.1.0', 'Language.TextToSpeech~~~fr-CA~0.0.1.0', 'Language.TextToSpeech~~~fr-CH~0.0.1.0', 'Language.TextToSpeech~~~fr-FR~0.0.1.0', 'Language.TextToSpeech~~~he-IL~0.0.1.0', 'Language.TextToSpeech~~~hi-IN~0.0.1.0', 'Language.TextToSpeech~~~hr-HR~0.0.1.0', 'Language.TextToSpeech~~~hu-HU~0.0.1.0', 'Language.TextToSpeech~~~id-ID~0.0.1.0', 'Language.TextToSpeech~~~it-IT~0.0.1.0', 'Language.TextToSpeech~~~ja-JP~0.0.1.0', 'Language.TextToSpeech~~~ko-KR~0.0.1.0', 'Language.TextToSpeech~~~ms-MY~0.0.1.0', 'Language.TextToSpeech~~~nb-NO~0.0.1.0', 'Language.TextToSpeech~~~nl-BE~0.0.1.0', 'Language.TextToSpeech~~~nl-NL~0.0.1.0', 'Language.TextToSpeech~~~pl-PL~0.0.1.0', 'Language.TextToSpeech~~~pt-BR~0.0.1.0', 'Language.TextToSpeech~~~pt-PT~0.0.1.0', 'Language.TextToSpeech~~~ro-RO~0.0.1.0', 'Language.TextToSpeech~~~ru-RU~0.0.1.0', 'Language.TextToSpeech~~~sk-SK~0.0.1.0', 'Language.TextToSpeech~~~sl-SI~0.0.1.0', 'Language.TextToSpeech~~~sv-SE~0.0.1.0', 'Language.TextToSpeech~~~ta-IN~0.0.1.0', 'Language.TextToSpeech~~~th-TH~0.0.1.0', 'Language.TextToSpeech~~~tr-TR~0.0.1.0', 'Language.TextToSpeech~~~vi-VN~0.0.1.0', 'Language.TextToSpeech~~~zh-CN~0.0.1.0', 'Language.TextToSpeech~~~zh-HK~0.0.1.0', 'Language.TextToSpeech~~~zh-TW~0.0.1.0', 'MathRecognizer~~~~0.0.1.0', 'Media.WindowsMediaPlayer~~~~0.0.12.0', 'Microsoft.Onecore.StorageManagement~~~~0.0.1.0', 'Microsoft.WebDriver~~~~0.0.1.0', 'Microsoft.Windows.StorageManagement~~~~0.0.1.0', 'Msix.PackagingTool.Driver~~~~0.0.1.0', 'NetFX3~~~~', 'Network.Irda~~~~0.0.1.0', 'OneCoreUAP.OneSync~~~~0.0.1.0', 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0', 'RasCMAK.Client~~~~0.0.1.0', 'RIP.Listener~~~~0.0.1.0', 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0', 'Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0', 'Rsat.CertificateServices.Tools~~~~0.0.1.0', 'Rsat.DHCP.Tools~~~~0.0.1.0', 'Rsat.Dns.Tools~~~~0.0.1.0', 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0', 'Rsat.FileServices.Tools~~~~0.0.1.0', 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0', 'Rsat.IPAM.Client.Tools~~~~0.0.1.0', 'Rsat.LLDP.Tools~~~~0.0.1.0', 'Rsat.NetworkController.Tools~~~~0.0.1.0', 'Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0', 'Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0', 'Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0', 'Rsat.ServerManager.Tools~~~~0.0.1.0', 'Rsat.Shielded.VM.Tools~~~~0.0.1.0', 'Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0', 'Rsat.StorageReplica.Tools~~~~0.0.1.0', 'Rsat.SystemInsights.Management.Tools~~~~0.0.1.0', 'Rsat.VolumeActivation.Tools~~~~0.0.1.0', 'Rsat.WSUS.Tools~~~~0.0.1.0', 'SNMP.Client~~~~0.0.1.0', 'Tools.DeveloperMode.Core~~~~0.0.1.0', 'Tools.DTrace.Platform~~~~0.0.1.0', 'Tools.Graphics.DirectX~~~~0.0.1.0', 'WMI-SNMP-Provider.Client~~~~0.0.1.0', 'XPS.Viewer~~~~0.0.1.0') $Win10_1903_FODs = @('Accessibility.Braille~~~~0.0.1.0', 'Analog.Holographic.Desktop~~~~0.0.1.0', 'App.Support.QuickAssist~~~~0.0.1.0', 'Browser.InternetExplorer~~~~0.0.11.0', 'Hello.Face.17658~~~~0.0.1.0', 'Hello.Face.Migration.17658~~~~0.0.1.0', 'Language.Basic~~~af-ZA~0.0.1.0', 'Language.Basic~~~ar-SA~0.0.1.0', 'Language.Basic~~~as-IN~0.0.1.0', 'Language.Basic~~~az-LATN-AZ~0.0.1.0', 'Language.Basic~~~ba-RU~0.0.1.0', 'Language.Basic~~~be-BY~0.0.1.0', 'Language.Basic~~~bg-BG~0.0.1.0', 'Language.Basic~~~bn-BD~0.0.1.0', 'Language.Basic~~~bn-IN~0.0.1.0', 'Language.Basic~~~bs-LATN-BA~0.0.1.0', 'Language.Basic~~~ca-ES~0.0.1.0', 'Language.Basic~~~cs-CZ~0.0.1.0', 'Language.Basic~~~cy-GB~0.0.1.0', 'Language.Basic~~~da-DK~0.0.1.0', 'Language.Basic~~~de-DE~0.0.1.0', 'Language.Basic~~~el-GR~0.0.1.0', 'Language.Basic~~~en-GB~0.0.1.0', 'Language.Basic~~~en-US~0.0.1.0', 'Language.Basic~~~es-ES~0.0.1.0', 'Language.Basic~~~es-MX~0.0.1.0', 'Language.Basic~~~et-EE~0.0.1.0', 'Language.Basic~~~eu-ES~0.0.1.0', 'Language.Basic~~~fa-IR~0.0.1.0', 'Language.Basic~~~fi-FI~0.0.1.0', 'Language.Basic~~~fil-PH~0.0.1.0', 'Language.Basic~~~fr-CA~0.0.1.0', 'Language.Basic~~~fr-FR~0.0.1.0', 'Language.Basic~~~ga-IE~0.0.1.0', 'Language.Basic~~~gd-GB~0.0.1.0', 'Language.Basic~~~gl-ES~0.0.1.0', 'Language.Basic~~~gu-IN~0.0.1.0', 'Language.Basic~~~ha-LATN-NG~0.0.1.0', 'Language.Basic~~~haw-US~0.0.1.0', 'Language.Basic~~~he-IL~0.0.1.0', 'Language.Basic~~~hi-IN~0.0.1.0', 'Language.Basic~~~hr-HR~0.0.1.0', 'Language.Basic~~~hu-HU~0.0.1.0', 'Language.Basic~~~hy-AM~0.0.1.0', 'Language.Basic~~~id-ID~0.0.1.0', 'Language.Basic~~~ig-NG~0.0.1.0', 'Language.Basic~~~is-IS~0.0.1.0', 'Language.Basic~~~it-IT~0.0.1.0', 'Language.Basic~~~ja-JP~0.0.1.0', 'Language.Basic~~~ka-GE~0.0.1.0', 'Language.Basic~~~kk-KZ~0.0.1.0', 'Language.Basic~~~kl-GL~0.0.1.0', 'Language.Basic~~~kn-IN~0.0.1.0', 'Language.Basic~~~ko-KR~0.0.1.0', 'Language.Basic~~~kok-DEVA-IN~0.0.1.0', 'Language.Basic~~~ky-KG~0.0.1.0', 'Language.Basic~~~lb-LU~0.0.1.0', 'Language.Basic~~~lt-LT~0.0.1.0', 'Language.Basic~~~lv-LV~0.0.1.0', 'Language.Basic~~~mi-NZ~0.0.1.0', 'Language.Basic~~~ml-IN~0.0.1.0', 'Language.Basic~~~mk-MK~0.0.1.0', 'Language.Basic~~~mn-MN~0.0.1.0', 'Language.Basic~~~mr-IN~0.0.1.0', 'Language.Basic~~~ms-BN~0.0.1.0', 'Language.Basic~~~ms-MY~0.0.1.0', 'Language.Basic~~~mt-MT~0.0.1.0', 'Language.Basic~~~nb-NO~0.0.1.0', 'Language.Basic~~~ne-NP~0.0.1.0', 'Language.Basic~~~nl-NL~0.0.1.0', 'Language.Basic~~~nn-NO~0.0.1.0', 'Language.Basic~~~nso-ZA~0.0.1.0', 'Language.Basic~~~or-IN~0.0.1.0', 'Language.Basic~~~pa-IN~0.0.1.0', 'Language.Basic~~~pl-PL~0.0.1.0', 'Language.Basic~~~ps-AF~0.0.1.0', 'Language.Basic~~~pt-BR~0.0.1.0', 'Language.Basic~~~pt-PT~0.0.1.0', 'Language.Basic~~~rm-CH~0.0.1.0', 'Language.Basic~~~ro-RO~0.0.1.0', 'Language.Basic~~~ru-RU~0.0.1.0', 'Language.Basic~~~rw-RW~0.0.1.0', 'Language.Basic~~~sah-RU~0.0.1.0', 'Language.Basic~~~si-LK~0.0.1.0', 'Language.Basic~~~sk-SK~0.0.1.0', 'Language.Basic~~~sl-SI~0.0.1.0', 'Language.Basic~~~sq-AL~0.0.1.0', 'Language.Basic~~~sr-CYRL-RS~0.0.1.0', 'Language.Basic~~~sr-LATN-RS~0.0.1.0', 'Language.Basic~~~sv-SE~0.0.1.0', 'Language.Basic~~~sw-KE~0.0.1.0', 'Language.Basic~~~ta-IN~0.0.1.0', 'Language.Basic~~~te-IN~0.0.1.0', 'Language.Basic~~~tg-CYRL-TJ~0.0.1.0', 'Language.Basic~~~th-TH~0.0.1.0', 'Language.Basic~~~tk-TM~0.0.1.0', 'Language.Basic~~~tn-ZA~0.0.1.0', 'Language.Basic~~~tr-TR~0.0.1.0', 'Language.Basic~~~tt-RU~0.0.1.0', 'Language.Basic~~~ug-CN~0.0.1.0', 'Language.Basic~~~uk-UA~0.0.1.0', 'Language.Basic~~~ur-PK~0.0.1.0', 'Language.Basic~~~uz-LATN-UZ~0.0.1.0', 'Language.Basic~~~vi-VN~0.0.1.0', 'Language.Basic~~~wo-SN~0.0.1.0', 'Language.Basic~~~xh-ZA~0.0.1.0', 'Language.Basic~~~yo-NG~0.0.1.0', 'Language.Basic~~~zh-CN~0.0.1.0', 'Language.Basic~~~zh-HK~0.0.1.0', 'Language.Basic~~~zh-TW~0.0.1.0', 'Language.Basic~~~zu-ZA~0.0.1.0', 'Language.Fonts.Arab~~~und-ARAB~0.0.1.0', 'Language.Fonts.Beng~~~und-BENG~0.0.1.0', 'Language.Fonts.Cans~~~und-CANS~0.0.1.0', 'Language.Fonts.Cher~~~und-CHER~0.0.1.0', 'Language.Fonts.Deva~~~und-DEVA~0.0.1.0', 'Language.Fonts.Ethi~~~und-ETHI~0.0.1.0', 'Language.Fonts.Gujr~~~und-GUJR~0.0.1.0', 'Language.Fonts.Guru~~~und-GURU~0.0.1.0', 'Language.Fonts.Hans~~~und-HANS~0.0.1.0', 'Language.Fonts.Hant~~~und-HANT~0.0.1.0', 'Language.Fonts.Hebr~~~und-HEBR~0.0.1.0', 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0', 'Language.Fonts.Khmr~~~und-KHMR~0.0.1.0', 'Language.Fonts.Knda~~~und-KNDA~0.0.1.0', 'Language.Fonts.Kore~~~und-KORE~0.0.1.0', 'Language.Fonts.Laoo~~~und-LAOO~0.0.1.0', 'Language.Fonts.Mlym~~~und-MLYM~0.0.1.0', 'Language.Fonts.Orya~~~und-ORYA~0.0.1.0', 'Language.Fonts.PanEuropeanSupplementalFonts~~~~0.0.1.0', 'Language.Fonts.Sinh~~~und-SINH~0.0.1.0', 'Language.Fonts.Syrc~~~und-SYRC~0.0.1.0', 'Language.Fonts.Taml~~~und-TAML~0.0.1.0', 'Language.Fonts.Telu~~~und-TELU~0.0.1.0', 'Language.Fonts.Thai~~~und-THAI~0.0.1.0', 'Language.Handwriting~~~af-ZA~0.0.1.0', 'Language.Handwriting~~~bs-LATN-BA~0.0.1.0', 'Language.Handwriting~~~ca-ES~0.0.1.0', 'Language.Handwriting~~~cs-CZ~0.0.1.0', 'Language.Handwriting~~~cy-GB~0.0.1.0', 'Language.Handwriting~~~da-DK~0.0.1.0', 'Language.Handwriting~~~de-DE~0.0.1.0', 'Language.Handwriting~~~el-GR~0.0.1.0', 'Language.Handwriting~~~en-GB~0.0.1.0', 'Language.Handwriting~~~en-US~0.0.1.0', 'Language.Handwriting~~~es-ES~0.0.1.0', 'Language.Handwriting~~~es-MX~0.0.1.0', 'Language.Handwriting~~~eu-ES~0.0.1.0', 'Language.Handwriting~~~fi-FI~0.0.1.0', 'Language.Handwriting~~~fr-FR~0.0.1.0', 'Language.Handwriting~~~ga-IE~0.0.1.0', 'Language.Handwriting~~~gd-GB~0.0.1.0', 'Language.Handwriting~~~gl-ES~0.0.1.0', 'Language.Handwriting~~~hi-IN~0.0.1.0', 'Language.Handwriting~~~hr-HR~0.0.1.0', 'Language.Handwriting~~~id-ID~0.0.1.0', 'Language.Handwriting~~~it-IT~0.0.1.0', 'Language.Handwriting~~~ja-JP~0.0.1.0', 'Language.Handwriting~~~ko-KR~0.0.1.0', 'Language.Handwriting~~~lb-LU~0.0.1.0', 'Language.Handwriting~~~mi-NZ~0.0.1.0', 'Language.Handwriting~~~ms-BN~0.0.1.0', 'Language.Handwriting~~~ms-MY~0.0.1.0', 'Language.Handwriting~~~nb-NO~0.0.1.0', 'Language.Handwriting~~~nl-NL~0.0.1.0', 'Language.Handwriting~~~nn-NO~0.0.1.0', 'Language.Handwriting~~~nso-ZA~0.0.1.0', 'Language.Handwriting~~~pl-PL~0.0.1.0', 'Language.Handwriting~~~pt-BR~0.0.1.0', 'Language.Handwriting~~~pt-PT~0.0.1.0', 'Language.Handwriting~~~rm-CH~0.0.1.0', 'Language.Handwriting~~~ro-RO~0.0.1.0', 'Language.Handwriting~~~ru-RU~0.0.1.0', 'Language.Handwriting~~~rw-RW~0.0.1.0', 'Language.Handwriting~~~sk-SK~0.0.1.0', 'Language.Handwriting~~~sl-SI~0.0.1.0', 'Language.Handwriting~~~sq-AL~0.0.1.0', 'Language.Handwriting~~~sr-CYRL-RS~0.0.1.0', 'Language.Handwriting~~~sr-LATN-RS~0.0.1.0', 'Language.Handwriting~~~sv-SE~0.0.1.0', 'Language.Handwriting~~~sw-KE~0.0.1.0', 'Language.Handwriting~~~tn-ZA~0.0.1.0', 'Language.Handwriting~~~tr-TR~0.0.1.0', 'Language.Handwriting~~~wo-SN~0.0.1.0', 'Language.Handwriting~~~xh-ZA~0.0.1.0', 'Language.Handwriting~~~zh-CN~0.0.1.0', 'Language.Handwriting~~~zh-HK~0.0.1.0', 'Language.Handwriting~~~zh-TW~0.0.1.0', 'Language.Handwriting~~~zu-ZA~0.0.1.0', 'Language.OCR~~~ar-SA~0.0.1.0', 'Language.OCR~~~bg-BG~0.0.1.0', 'Language.OCR~~~bs-LATN-BA~0.0.1.0', 'Language.OCR~~~cs-CZ~0.0.1.0', 'Language.OCR~~~da-DK~0.0.1.0', 'Language.OCR~~~de-DE~0.0.1.0', 'Language.OCR~~~el-GR~0.0.1.0', 'Language.OCR~~~en-GB~0.0.1.0', 'Language.OCR~~~en-US~0.0.1.0', 'Language.OCR~~~es-ES~0.0.1.0', 'Language.OCR~~~es-MX~0.0.1.0', 'Language.OCR~~~fi-FI~0.0.1.0', 'Language.OCR~~~fr-CA~0.0.1.0', 'Language.OCR~~~fr-FR~0.0.1.0', 'Language.OCR~~~hr-HR~0.0.1.0', 'Language.OCR~~~hu-HU~0.0.1.0', 'Language.OCR~~~it-IT~0.0.1.0', 'Language.OCR~~~ja-JP~0.0.1.0', 'Language.OCR~~~ko-KR~0.0.1.0', 'Language.OCR~~~nb-NO~0.0.1.0', 'Language.OCR~~~nl-NL~0.0.1.0', 'Language.OCR~~~pl-PL~0.0.1.0', 'Language.OCR~~~pt-BR~0.0.1.0', 'Language.OCR~~~pt-PT~0.0.1.0', 'Language.OCR~~~ro-RO~0.0.1.0', 'Language.OCR~~~ru-RU~0.0.1.0', 'Language.OCR~~~sk-SK~0.0.1.0', 'Language.OCR~~~sl-SI~0.0.1.0', 'Language.OCR~~~sr-CYRL-RS~0.0.1.0', 'Language.OCR~~~sr-LATN-RS~0.0.1.0', 'Language.OCR~~~sv-SE~0.0.1.0', 'Language.OCR~~~tr-TR~0.0.1.0', 'Language.OCR~~~zh-CN~0.0.1.0', 'Language.OCR~~~zh-HK~0.0.1.0', 'Language.OCR~~~zh-TW~0.0.1.0', 'Language.Speech~~~de-DE~0.0.1.0', 'Language.Speech~~~en-AU~0.0.1.0', 'Language.Speech~~~en-CA~0.0.1.0', 'Language.Speech~~~en-GB~0.0.1.0', 'Language.Speech~~~en-IN~0.0.1.0', 'Language.Speech~~~en-US~0.0.1.0', 'Language.Speech~~~es-ES~0.0.1.0', 'Language.Speech~~~es-MX~0.0.1.0', 'Language.Speech~~~fr-CA~0.0.1.0', 'Language.Speech~~~fr-FR~0.0.1.0', 'Language.Speech~~~it-IT~0.0.1.0', 'Language.Speech~~~ja-JP~0.0.1.0', 'Language.Speech~~~pt-BR~0.0.1.0', 'Language.Speech~~~zh-CN~0.0.1.0', 'Language.Speech~~~zh-HK~0.0.1.0', 'Language.Speech~~~zh-TW~0.0.1.0', 'Language.TextToSpeech~~~ar-EG~0.0.1.0', 'Language.TextToSpeech~~~ar-SA~0.0.1.0', 'Language.TextToSpeech~~~bg-BG~0.0.1.0', 'Language.TextToSpeech~~~ca-ES~0.0.1.0', 'Language.TextToSpeech~~~cs-CZ~0.0.1.0', 'Language.TextToSpeech~~~da-DK~0.0.1.0', 'Language.TextToSpeech~~~de-AT~0.0.1.0', 'Language.TextToSpeech~~~de-CH~0.0.1.0', 'Language.TextToSpeech~~~de-DE~0.0.1.0', 'Language.TextToSpeech~~~el-GR~0.0.1.0', 'Language.TextToSpeech~~~en-AU~0.0.1.0', 'Language.TextToSpeech~~~en-CA~0.0.1.0', 'Language.TextToSpeech~~~en-GB~0.0.1.0', 'Language.TextToSpeech~~~en-IE~0.0.1.0', 'Language.TextToSpeech~~~en-IN~0.0.1.0', 'Language.TextToSpeech~~~en-US~0.0.1.0', 'Language.TextToSpeech~~~es-ES~0.0.1.0', 'Language.TextToSpeech~~~es-MX~0.0.1.0', 'Language.TextToSpeech~~~fi-FI~0.0.1.0', 'Language.TextToSpeech~~~fr-CA~0.0.1.0', 'Language.TextToSpeech~~~fr-CH~0.0.1.0', 'Language.TextToSpeech~~~fr-FR~0.0.1.0', 'Language.TextToSpeech~~~he-IL~0.0.1.0', 'Language.TextToSpeech~~~hi-IN~0.0.1.0', 'Language.TextToSpeech~~~hr-HR~0.0.1.0', 'Language.TextToSpeech~~~hu-HU~0.0.1.0', 'Language.TextToSpeech~~~id-ID~0.0.1.0', 'Language.TextToSpeech~~~it-IT~0.0.1.0', 'Language.TextToSpeech~~~ja-JP~0.0.1.0', 'Language.TextToSpeech~~~ko-KR~0.0.1.0', 'Language.TextToSpeech~~~ms-MY~0.0.1.0', 'Language.TextToSpeech~~~nb-NO~0.0.1.0', 'Language.TextToSpeech~~~nl-BE~0.0.1.0', 'Language.TextToSpeech~~~nl-NL~0.0.1.0', 'Language.TextToSpeech~~~pl-PL~0.0.1.0', 'Language.TextToSpeech~~~pt-BR~0.0.1.0', 'Language.TextToSpeech~~~pt-PT~0.0.1.0', 'Language.TextToSpeech~~~ro-RO~0.0.1.0', 'Language.TextToSpeech~~~ru-RU~0.0.1.0', 'Language.TextToSpeech~~~sk-SK~0.0.1.0', 'Language.TextToSpeech~~~sl-SI~0.0.1.0', 'Language.TextToSpeech~~~sv-SE~0.0.1.0', 'Language.TextToSpeech~~~ta-IN~0.0.1.0', 'Language.TextToSpeech~~~th-TH~0.0.1.0', 'Language.TextToSpeech~~~tr-TR~0.0.1.0', 'Language.TextToSpeech~~~vi-VN~0.0.1.0', 'Language.TextToSpeech~~~zh-CN~0.0.1.0', 'Language.TextToSpeech~~~zh-HK~0.0.1.0', 'Language.TextToSpeech~~~zh-TW~0.0.1.0', 'MathRecognizer~~~~0.0.1.0', 'Media.WindowsMediaPlayer~~~~0.0.12.0', 'Microsoft.Onecore.StorageManagement~~~~0.0.1.0', 'Microsoft.WebDriver~~~~0.0.1.0', 'Microsoft.Windows.StorageManagement~~~~0.0.1.0', 'Msix.PackagingTool.Driver~~~~0.0.1.0', 'NetFX3~~~~', 'OneCoreUAP.OneSync~~~~0.0.1.0', 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0', 'RasCMAK.Client~~~~0.0.1.0', 'RIP.Listener~~~~0.0.1.0', 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0', 'Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0', 'Rsat.CertificateServices.Tools~~~~0.0.1.0', 'Rsat.DHCP.Tools~~~~0.0.1.0', 'Rsat.Dns.Tools~~~~0.0.1.0', 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0', 'Rsat.FileServices.Tools~~~~0.0.1.0', 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0', 'Rsat.IPAM.Client.Tools~~~~0.0.1.0', 'Rsat.LLDP.Tools~~~~0.0.1.0', 'Rsat.NetworkController.Tools~~~~0.0.1.0', 'Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0', 'Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0', 'Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0', 'Rsat.ServerManager.Tools~~~~0.0.1.0', 'Rsat.Shielded.VM.Tools~~~~0.0.1.0', 'Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0', 'Rsat.StorageReplica.Tools~~~~0.0.1.0', 'Rsat.SystemInsights.Management.Tools~~~~0.0.1.0', 'Rsat.VolumeActivation.Tools~~~~0.0.1.0', 'Rsat.WSUS.Tools~~~~0.0.1.0', 'SNMP.Client~~~~0.0.1.0', 'Tools.DeveloperMode.Core~~~~0.0.1.0', 'Tools.DTrace.Platform~~~~0.0.1.0', 'Tools.Graphics.DirectX~~~~0.0.1.0', 'WMI-SNMP-Provider.Client~~~~0.0.1.0', 'XPS.Viewer~~~~0.0.1.0') $Win10_1809_FODs = @('Accessibility.Braille~~~~0.0.1.0', 'Analog.Holographic.Desktop~~~~0.0.1.0', 'App.Support.QuickAssist~~~~0.0.1.0', 'Browser.InternetExplorer~~~~0.0.11.0', 'Hello.Face.17658~~~~0.0.1.0', 'Hello.Face.Migration.17658~~~~0.0.1.0', 'Language.Basic~~~af-ZA~0.0.1.0', 'Language.Basic~~~ar-SA~0.0.1.0', 'Language.Basic~~~as-IN~0.0.1.0', 'Language.Basic~~~az-LATN-AZ~0.0.1.0', 'Language.Basic~~~ba-RU~0.0.1.0', 'Language.Basic~~~be-BY~0.0.1.0', 'Language.Basic~~~bg-BG~0.0.1.0', 'Language.Basic~~~bn-BD~0.0.1.0', 'Language.Basic~~~bn-IN~0.0.1.0', 'Language.Basic~~~bs-LATN-BA~0.0.1.0', 'Language.Basic~~~ca-ES~0.0.1.0', 'Language.Basic~~~cs-CZ~0.0.1.0', 'Language.Basic~~~cy-GB~0.0.1.0', 'Language.Basic~~~da-DK~0.0.1.0', 'Language.Basic~~~de-DE~0.0.1.0', 'Language.Basic~~~el-GR~0.0.1.0', 'Language.Basic~~~en-GB~0.0.1.0', 'Language.Basic~~~en-US~0.0.1.0', 'Language.Basic~~~es-ES~0.0.1.0', 'Language.Basic~~~es-MX~0.0.1.0', 'Language.Basic~~~et-EE~0.0.1.0', 'Language.Basic~~~eu-ES~0.0.1.0', 'Language.Basic~~~fa-IR~0.0.1.0', 'Language.Basic~~~fi-FI~0.0.1.0', 'Language.Basic~~~fil-PH~0.0.1.0', 'Language.Basic~~~fr-CA~0.0.1.0', 'Language.Basic~~~fr-FR~0.0.1.0', 'Language.Basic~~~ga-IE~0.0.1.0', 'Language.Basic~~~gd-GB~0.0.1.0', 'Language.Basic~~~gl-ES~0.0.1.0', 'Language.Basic~~~gu-IN~0.0.1.0', 'Language.Basic~~~ha-LATN-NG~0.0.1.0', 'Language.Basic~~~haw-US~0.0.1.0', 'Language.Basic~~~he-IL~0.0.1.0', 'Language.Basic~~~hi-IN~0.0.1.0', 'Language.Basic~~~hr-HR~0.0.1.0', 'Language.Basic~~~hu-HU~0.0.1.0', 'Language.Basic~~~hy-AM~0.0.1.0', 'Language.Basic~~~id-ID~0.0.1.0', 'Language.Basic~~~ig-NG~0.0.1.0', 'Language.Basic~~~is-IS~0.0.1.0', 'Language.Basic~~~it-IT~0.0.1.0', 'Language.Basic~~~ja-JP~0.0.1.0', 'Language.Basic~~~ka-GE~0.0.1.0', 'Language.Basic~~~kk-KZ~0.0.1.0', 'Language.Basic~~~kl-GL~0.0.1.0', 'Language.Basic~~~kn-IN~0.0.1.0', 'Language.Basic~~~ko-KR~0.0.1.0', 'Language.Basic~~~kok-DEVA-IN~0.0.1.0', 'Language.Basic~~~ky-KG~0.0.1.0', 'Language.Basic~~~lb-LU~0.0.1.0', 'Language.Basic~~~lt-LT~0.0.1.0', 'Language.Basic~~~lv-LV~0.0.1.0', 'Language.Basic~~~mi-NZ~0.0.1.0', 'Language.Basic~~~ml-IN~0.0.1.0', 'Language.Basic~~~mk-MK~0.0.1.0', 'Language.Basic~~~mn-MN~0.0.1.0', 'Language.Basic~~~mr-IN~0.0.1.0', 'Language.Basic~~~ms-BN~0.0.1.0', 'Language.Basic~~~ms-MY~0.0.1.0', 'Language.Basic~~~mt-MT~0.0.1.0', 'Language.Basic~~~nb-NO~0.0.1.0', 'Language.Basic~~~ne-NP~0.0.1.0', 'Language.Basic~~~nl-NL~0.0.1.0', 'Language.Basic~~~nn-NO~0.0.1.0', 'Language.Basic~~~nso-ZA~0.0.1.0', 'Language.Basic~~~or-IN~0.0.1.0', 'Language.Basic~~~pa-IN~0.0.1.0', 'Language.Basic~~~pl-PL~0.0.1.0', 'Language.Basic~~~ps-AF~0.0.1.0', 'Language.Basic~~~pt-BR~0.0.1.0', 'Language.Basic~~~pt-PT~0.0.1.0', 'Language.Basic~~~rm-CH~0.0.1.0', 'Language.Basic~~~ro-RO~0.0.1.0', 'Language.Basic~~~ru-RU~0.0.1.0', 'Language.Basic~~~rw-RW~0.0.1.0', 'Language.Basic~~~sah-RU~0.0.1.0', 'Language.Basic~~~si-LK~0.0.1.0', 'Language.Basic~~~sk-SK~0.0.1.0', 'Language.Basic~~~sl-SI~0.0.1.0', 'Language.Basic~~~sq-AL~0.0.1.0', 'Language.Basic~~~sr-CYRL-RS~0.0.1.0', 'Language.Basic~~~sr-LATN-RS~0.0.1.0', 'Language.Basic~~~sv-SE~0.0.1.0', 'Language.Basic~~~sw-KE~0.0.1.0', 'Language.Basic~~~ta-IN~0.0.1.0', 'Language.Basic~~~te-IN~0.0.1.0', 'Language.Basic~~~tg-CYRL-TJ~0.0.1.0', 'Language.Basic~~~th-TH~0.0.1.0', 'Language.Basic~~~tk-TM~0.0.1.0', 'Language.Basic~~~tn-ZA~0.0.1.0', 'Language.Basic~~~tr-TR~0.0.1.0', 'Language.Basic~~~tt-RU~0.0.1.0', 'Language.Basic~~~ug-CN~0.0.1.0', 'Language.Basic~~~uk-UA~0.0.1.0', 'Language.Basic~~~ur-PK~0.0.1.0', 'Language.Basic~~~uz-LATN-UZ~0.0.1.0', 'Language.Basic~~~vi-VN~0.0.1.0', 'Language.Basic~~~wo-SN~0.0.1.0', 'Language.Basic~~~xh-ZA~0.0.1.0', 'Language.Basic~~~yo-NG~0.0.1.0', 'Language.Basic~~~zh-CN~0.0.1.0', 'Language.Basic~~~zh-HK~0.0.1.0', 'Language.Basic~~~zh-TW~0.0.1.0', 'Language.Basic~~~zu-ZA~0.0.1.0', 'Language.Fonts.Arab~~~und-ARAB~0.0.1.0', 'Language.Fonts.Beng~~~und-BENG~0.0.1.0', 'Language.Fonts.Cans~~~und-CANS~0.0.1.0', 'Language.Fonts.Cher~~~und-CHER~0.0.1.0', 'Language.Fonts.Deva~~~und-DEVA~0.0.1.0', 'Language.Fonts.Ethi~~~und-ETHI~0.0.1.0', 'Language.Fonts.Gujr~~~und-GUJR~0.0.1.0', 'Language.Fonts.Guru~~~und-GURU~0.0.1.0', 'Language.Fonts.Hans~~~und-HANS~0.0.1.0', 'Language.Fonts.Hant~~~und-HANT~0.0.1.0', 'Language.Fonts.Hebr~~~und-HEBR~0.0.1.0', 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0', 'Language.Fonts.Khmr~~~und-KHMR~0.0.1.0', 'Language.Fonts.Knda~~~und-KNDA~0.0.1.0', 'Language.Fonts.Kore~~~und-KORE~0.0.1.0', 'Language.Fonts.Laoo~~~und-LAOO~0.0.1.0', 'Language.Fonts.Mlym~~~und-MLYM~0.0.1.0', 'Language.Fonts.Orya~~~und-ORYA~0.0.1.0', 'Language.Fonts.PanEuropeanSupplementalFonts~~~~0.0.1.0', 'Language.Fonts.Sinh~~~und-SINH~0.0.1.0', 'Language.Fonts.Syrc~~~und-SYRC~0.0.1.0', 'Language.Fonts.Taml~~~und-TAML~0.0.1.0', 'Language.Fonts.Telu~~~und-TELU~0.0.1.0', 'Language.Fonts.Thai~~~und-THAI~0.0.1.0', 'Language.Handwriting~~~af-ZA~0.0.1.0', 'Language.Handwriting~~~bs-LATN-BA~0.0.1.0', 'Language.Handwriting~~~ca-ES~0.0.1.0', 'Language.Handwriting~~~cs-CZ~0.0.1.0', 'Language.Handwriting~~~cy-GB~0.0.1.0', 'Language.Handwriting~~~da-DK~0.0.1.0', 'Language.Handwriting~~~de-DE~0.0.1.0', 'Language.Handwriting~~~el-GR~0.0.1.0', 'Language.Handwriting~~~en-GB~0.0.1.0', 'Language.Handwriting~~~en-US~0.0.1.0', 'Language.Handwriting~~~es-ES~0.0.1.0', 'Language.Handwriting~~~es-MX~0.0.1.0', 'Language.Handwriting~~~eu-ES~0.0.1.0', 'Language.Handwriting~~~fi-FI~0.0.1.0', 'Language.Handwriting~~~fr-FR~0.0.1.0', 'Language.Handwriting~~~ga-IE~0.0.1.0', 'Language.Handwriting~~~gd-GB~0.0.1.0', 'Language.Handwriting~~~gl-ES~0.0.1.0', 'Language.Handwriting~~~hi-IN~0.0.1.0', 'Language.Handwriting~~~hr-HR~0.0.1.0', 'Language.Handwriting~~~id-ID~0.0.1.0', 'Language.Handwriting~~~it-IT~0.0.1.0', 'Language.Handwriting~~~ja-JP~0.0.1.0', 'Language.Handwriting~~~ko-KR~0.0.1.0', 'Language.Handwriting~~~lb-LU~0.0.1.0', 'Language.Handwriting~~~mi-NZ~0.0.1.0', 'Language.Handwriting~~~ms-BN~0.0.1.0', 'Language.Handwriting~~~ms-MY~0.0.1.0', 'Language.Handwriting~~~nb-NO~0.0.1.0', 'Language.Handwriting~~~nl-NL~0.0.1.0', 'Language.Handwriting~~~nn-NO~0.0.1.0', 'Language.Handwriting~~~nso-ZA~0.0.1.0', 'Language.Handwriting~~~pl-PL~0.0.1.0', 'Language.Handwriting~~~pt-BR~0.0.1.0', 'Language.Handwriting~~~pt-PT~0.0.1.0', 'Language.Handwriting~~~rm-CH~0.0.1.0', 'Language.Handwriting~~~ro-RO~0.0.1.0', 'Language.Handwriting~~~ru-RU~0.0.1.0', 'Language.Handwriting~~~rw-RW~0.0.1.0', 'Language.Handwriting~~~sk-SK~0.0.1.0', 'Language.Handwriting~~~sl-SI~0.0.1.0', 'Language.Handwriting~~~sq-AL~0.0.1.0', 'Language.Handwriting~~~sr-CYRL-RS~0.0.1.0', 'Language.Handwriting~~~sr-LATN-RS~0.0.1.0', 'Language.Handwriting~~~sv-SE~0.0.1.0', 'Language.Handwriting~~~sw-KE~0.0.1.0', 'Language.Handwriting~~~tn-ZA~0.0.1.0', 'Language.Handwriting~~~tr-TR~0.0.1.0', 'Language.Handwriting~~~wo-SN~0.0.1.0', 'Language.Handwriting~~~xh-ZA~0.0.1.0', 'Language.Handwriting~~~zh-CN~0.0.1.0', 'Language.Handwriting~~~zh-HK~0.0.1.0', 'Language.Handwriting~~~zh-TW~0.0.1.0', 'Language.Handwriting~~~zu-ZA~0.0.1.0', 'Language.OCR~~~ar-SA~0.0.1.0', 'Language.OCR~~~bg-BG~0.0.1.0', 'Language.OCR~~~bs-LATN-BA~0.0.1.0', 'Language.OCR~~~cs-CZ~0.0.1.0', 'Language.OCR~~~da-DK~0.0.1.0', 'Language.OCR~~~de-DE~0.0.1.0', 'Language.OCR~~~el-GR~0.0.1.0', 'Language.OCR~~~en-GB~0.0.1.0', 'Language.OCR~~~en-US~0.0.1.0', 'Language.OCR~~~es-ES~0.0.1.0', 'Language.OCR~~~es-MX~0.0.1.0', 'Language.OCR~~~fi-FI~0.0.1.0', 'Language.OCR~~~fr-CA~0.0.1.0', 'Language.OCR~~~fr-FR~0.0.1.0', 'Language.OCR~~~hr-HR~0.0.1.0', 'Language.OCR~~~hu-HU~0.0.1.0', 'Language.OCR~~~it-IT~0.0.1.0', 'Language.OCR~~~ja-JP~0.0.1.0', 'Language.OCR~~~ko-KR~0.0.1.0', 'Language.OCR~~~nb-NO~0.0.1.0', 'Language.OCR~~~nl-NL~0.0.1.0', 'Language.OCR~~~pl-PL~0.0.1.0', 'Language.OCR~~~pt-BR~0.0.1.0', 'Language.OCR~~~pt-PT~0.0.1.0', 'Language.OCR~~~ro-RO~0.0.1.0', 'Language.OCR~~~ru-RU~0.0.1.0', 'Language.OCR~~~sk-SK~0.0.1.0', 'Language.OCR~~~sl-SI~0.0.1.0', 'Language.OCR~~~sr-CYRL-RS~0.0.1.0', 'Language.OCR~~~sr-LATN-RS~0.0.1.0', 'Language.OCR~~~sv-SE~0.0.1.0', 'Language.OCR~~~tr-TR~0.0.1.0', 'Language.OCR~~~zh-CN~0.0.1.0', 'Language.OCR~~~zh-HK~0.0.1.0', 'Language.OCR~~~zh-TW~0.0.1.0', 'Language.Speech~~~de-DE~0.0.1.0', 'Language.Speech~~~en-AU~0.0.1.0', 'Language.Speech~~~en-CA~0.0.1.0', 'Language.Speech~~~en-GB~0.0.1.0', 'Language.Speech~~~en-IN~0.0.1.0', 'Language.Speech~~~en-US~0.0.1.0', 'Language.Speech~~~es-ES~0.0.1.0', 'Language.Speech~~~es-MX~0.0.1.0', 'Language.Speech~~~fr-CA~0.0.1.0', 'Language.Speech~~~fr-FR~0.0.1.0', 'Language.Speech~~~it-IT~0.0.1.0', 'Language.Speech~~~ja-JP~0.0.1.0', 'Language.Speech~~~pt-BR~0.0.1.0', 'Language.Speech~~~zh-CN~0.0.1.0', 'Language.Speech~~~zh-HK~0.0.1.0', 'Language.Speech~~~zh-TW~0.0.1.0', 'Language.TextToSpeech~~~ar-EG~0.0.1.0', 'Language.TextToSpeech~~~ar-SA~0.0.1.0', 'Language.TextToSpeech~~~bg-BG~0.0.1.0', 'Language.TextToSpeech~~~ca-ES~0.0.1.0', 'Language.TextToSpeech~~~cs-CZ~0.0.1.0', 'Language.TextToSpeech~~~da-DK~0.0.1.0', 'Language.TextToSpeech~~~de-AT~0.0.1.0', 'Language.TextToSpeech~~~de-CH~0.0.1.0', 'Language.TextToSpeech~~~de-DE~0.0.1.0', 'Language.TextToSpeech~~~el-GR~0.0.1.0', 'Language.TextToSpeech~~~en-AU~0.0.1.0', 'Language.TextToSpeech~~~en-CA~0.0.1.0', 'Language.TextToSpeech~~~en-GB~0.0.1.0', 'Language.TextToSpeech~~~en-IE~0.0.1.0', 'Language.TextToSpeech~~~en-IN~0.0.1.0', 'Language.TextToSpeech~~~en-US~0.0.1.0', 'Language.TextToSpeech~~~es-ES~0.0.1.0', 'Language.TextToSpeech~~~es-MX~0.0.1.0', 'Language.TextToSpeech~~~fi-FI~0.0.1.0', 'Language.TextToSpeech~~~fr-CA~0.0.1.0', 'Language.TextToSpeech~~~fr-CH~0.0.1.0', 'Language.TextToSpeech~~~fr-FR~0.0.1.0', 'Language.TextToSpeech~~~he-IL~0.0.1.0', 'Language.TextToSpeech~~~hi-IN~0.0.1.0', 'Language.TextToSpeech~~~hr-HR~0.0.1.0', 'Language.TextToSpeech~~~hu-HU~0.0.1.0', 'Language.TextToSpeech~~~id-ID~0.0.1.0', 'Language.TextToSpeech~~~it-IT~0.0.1.0', 'Language.TextToSpeech~~~ja-JP~0.0.1.0', 'Language.TextToSpeech~~~ko-KR~0.0.1.0', 'Language.TextToSpeech~~~ms-MY~0.0.1.0', 'Language.TextToSpeech~~~nb-NO~0.0.1.0', 'Language.TextToSpeech~~~nl-BE~0.0.1.0', 'Language.TextToSpeech~~~nl-NL~0.0.1.0', 'Language.TextToSpeech~~~pl-PL~0.0.1.0', 'Language.TextToSpeech~~~pt-BR~0.0.1.0', 'Language.TextToSpeech~~~pt-PT~0.0.1.0', 'Language.TextToSpeech~~~ro-RO~0.0.1.0', 'Language.TextToSpeech~~~ru-RU~0.0.1.0', 'Language.TextToSpeech~~~sk-SK~0.0.1.0', 'Language.TextToSpeech~~~sl-SI~0.0.1.0', 'Language.TextToSpeech~~~sv-SE~0.0.1.0', 'Language.TextToSpeech~~~ta-IN~0.0.1.0', 'Language.TextToSpeech~~~th-TH~0.0.1.0', 'Language.TextToSpeech~~~tr-TR~0.0.1.0', 'Language.TextToSpeech~~~vi-VN~0.0.1.0', 'Language.TextToSpeech~~~zh-CN~0.0.1.0', 'Language.TextToSpeech~~~zh-HK~0.0.1.0', 'Language.TextToSpeech~~~zh-TW~0.0.1.0', 'MathRecognizer~~~~0.0.1.0', 'Media.WindowsMediaPlayer~~~~0.0.12.0', 'Microsoft.Onecore.StorageManagement~~~~0.0.1.0', 'Microsoft.WebDriver~~~~0.0.1.0', 'Microsoft.Windows.StorageManagement~~~~0.0.1.0', 'Msix.PackagingTool.Driver~~~~0.0.1.0', 'NetFX3~~~~' 'OneCoreUAP.OneSync~~~~0.0.1.0', 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0', 'RasCMAK.Client~~~~0.0.1.0', 'RIP.Listener~~~~0.0.1.0', 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0', 'Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0', 'Rsat.CertificateServices.Tools~~~~0.0.1.0', 'Rsat.DHCP.Tools~~~~0.0.1.0', 'Rsat.Dns.Tools~~~~0.0.1.0', 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0', 'Rsat.FileServices.Tools~~~~0.0.1.0', 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0', 'Rsat.IPAM.Client.Tools~~~~0.0.1.0', 'Rsat.LLDP.Tools~~~~0.0.1.0', 'Rsat.NetworkController.Tools~~~~0.0.1.0', 'Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0', 'Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0', 'Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0', 'Rsat.ServerManager.Tools~~~~0.0.1.0', 'Rsat.Shielded.VM.Tools~~~~0.0.1.0', 'Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0', 'Rsat.StorageReplica.Tools~~~~0.0.1.0', 'Rsat.SystemInsights.Management.Tools~~~~0.0.1.0', 'Rsat.VolumeActivation.Tools~~~~0.0.1.0', 'Rsat.WSUS.Tools~~~~0.0.1.0', 'SNMP.Client~~~~0.0.1.0', 'Tools.DeveloperMode.Core~~~~0.0.1.0', 'Tools.DTrace.Platform~~~~0.0.1.0', 'Tools.Graphics.DirectX~~~~0.0.1.0', 'WMI-SNMP-Provider.Client~~~~0.0.1.0', 'XPS.Viewer~~~~0.0.1.0' ) $Win10_1809_server_FODs = @('Accessibility.Braille~~~~0.0.1.0', 'Analog.Holographic.Desktop~~~~0.0.1.0', 'App.Support.QuickAssist~~~~0.0.1.0', 'Browser.InternetExplorer~~~~0.0.11.0', 'Hello.Face.17658~~~~0.0.1.0', 'Hello.Face.Migration.17658~~~~0.0.1.0', 'Language.Basic~~~af-ZA~0.0.1.0', 'Language.Basic~~~ar-SA~0.0.1.0', 'Language.Basic~~~as-IN~0.0.1.0', 'Language.Basic~~~az-LATN-AZ~0.0.1.0', 'Language.Basic~~~ba-RU~0.0.1.0', 'Language.Basic~~~be-BY~0.0.1.0', 'Language.Basic~~~bg-BG~0.0.1.0', 'Language.Basic~~~bn-BD~0.0.1.0', 'Language.Basic~~~bn-IN~0.0.1.0', 'Language.Basic~~~bs-LATN-BA~0.0.1.0', 'Language.Basic~~~ca-ES~0.0.1.0', 'Language.Basic~~~cs-CZ~0.0.1.0', 'Language.Basic~~~cy-GB~0.0.1.0', 'Language.Basic~~~da-DK~0.0.1.0', 'Language.Basic~~~de-DE~0.0.1.0', 'Language.Basic~~~el-GR~0.0.1.0', 'Language.Basic~~~en-GB~0.0.1.0', 'Language.Basic~~~en-US~0.0.1.0', 'Language.Basic~~~es-ES~0.0.1.0', 'Language.Basic~~~es-MX~0.0.1.0', 'Language.Basic~~~et-EE~0.0.1.0', 'Language.Basic~~~eu-ES~0.0.1.0', 'Language.Basic~~~fa-IR~0.0.1.0', 'Language.Basic~~~fi-FI~0.0.1.0', 'Language.Basic~~~fil-PH~0.0.1.0', 'Language.Basic~~~fr-CA~0.0.1.0', 'Language.Basic~~~fr-FR~0.0.1.0', 'Language.Basic~~~ga-IE~0.0.1.0', 'Language.Basic~~~gd-GB~0.0.1.0', 'Language.Basic~~~gl-ES~0.0.1.0', 'Language.Basic~~~gu-IN~0.0.1.0', 'Language.Basic~~~ha-LATN-NG~0.0.1.0', 'Language.Basic~~~haw-US~0.0.1.0', 'Language.Basic~~~he-IL~0.0.1.0', 'Language.Basic~~~hi-IN~0.0.1.0', 'Language.Basic~~~hr-HR~0.0.1.0', 'Language.Basic~~~hu-HU~0.0.1.0', 'Language.Basic~~~hy-AM~0.0.1.0', 'Language.Basic~~~id-ID~0.0.1.0', 'Language.Basic~~~ig-NG~0.0.1.0', 'Language.Basic~~~is-IS~0.0.1.0', 'Language.Basic~~~it-IT~0.0.1.0', 'Language.Basic~~~ja-JP~0.0.1.0', 'Language.Basic~~~ka-GE~0.0.1.0', 'Language.Basic~~~kk-KZ~0.0.1.0', 'Language.Basic~~~kl-GL~0.0.1.0', 'Language.Basic~~~kn-IN~0.0.1.0', 'Language.Basic~~~ko-KR~0.0.1.0', 'Language.Basic~~~kok-DEVA-IN~0.0.1.0', 'Language.Basic~~~ky-KG~0.0.1.0', 'Language.Basic~~~lb-LU~0.0.1.0', 'Language.Basic~~~lt-LT~0.0.1.0', 'Language.Basic~~~lv-LV~0.0.1.0', 'Language.Basic~~~mi-NZ~0.0.1.0', 'Language.Basic~~~ml-IN~0.0.1.0', 'Language.Basic~~~mk-MK~0.0.1.0', 'Language.Basic~~~mn-MN~0.0.1.0', 'Language.Basic~~~mr-IN~0.0.1.0', 'Language.Basic~~~ms-BN~0.0.1.0', 'Language.Basic~~~ms-MY~0.0.1.0', 'Language.Basic~~~mt-MT~0.0.1.0', 'Language.Basic~~~nb-NO~0.0.1.0', 'Language.Basic~~~ne-NP~0.0.1.0', 'Language.Basic~~~nl-NL~0.0.1.0', 'Language.Basic~~~nn-NO~0.0.1.0', 'Language.Basic~~~nso-ZA~0.0.1.0', 'Language.Basic~~~or-IN~0.0.1.0', 'Language.Basic~~~pa-IN~0.0.1.0', 'Language.Basic~~~pl-PL~0.0.1.0', 'Language.Basic~~~ps-AF~0.0.1.0', 'Language.Basic~~~pt-BR~0.0.1.0', 'Language.Basic~~~pt-PT~0.0.1.0', 'Language.Basic~~~rm-CH~0.0.1.0', 'Language.Basic~~~ro-RO~0.0.1.0', 'Language.Basic~~~ru-RU~0.0.1.0', 'Language.Basic~~~rw-RW~0.0.1.0', 'Language.Basic~~~sah-RU~0.0.1.0', 'Language.Basic~~~si-LK~0.0.1.0', 'Language.Basic~~~sk-SK~0.0.1.0', 'Language.Basic~~~sl-SI~0.0.1.0', 'Language.Basic~~~sq-AL~0.0.1.0', 'Language.Basic~~~sr-CYRL-RS~0.0.1.0', 'Language.Basic~~~sr-LATN-RS~0.0.1.0', 'Language.Basic~~~sv-SE~0.0.1.0', 'Language.Basic~~~sw-KE~0.0.1.0', 'Language.Basic~~~ta-IN~0.0.1.0', 'Language.Basic~~~te-IN~0.0.1.0', 'Language.Basic~~~tg-CYRL-TJ~0.0.1.0', 'Language.Basic~~~th-TH~0.0.1.0', 'Language.Basic~~~tk-TM~0.0.1.0', 'Language.Basic~~~tn-ZA~0.0.1.0', 'Language.Basic~~~tr-TR~0.0.1.0', 'Language.Basic~~~tt-RU~0.0.1.0', 'Language.Basic~~~ug-CN~0.0.1.0', 'Language.Basic~~~uk-UA~0.0.1.0', 'Language.Basic~~~ur-PK~0.0.1.0', 'Language.Basic~~~uz-LATN-UZ~0.0.1.0', 'Language.Basic~~~vi-VN~0.0.1.0', 'Language.Basic~~~wo-SN~0.0.1.0', 'Language.Basic~~~xh-ZA~0.0.1.0', 'Language.Basic~~~yo-NG~0.0.1.0', 'Language.Basic~~~zh-CN~0.0.1.0', 'Language.Basic~~~zh-HK~0.0.1.0', 'Language.Basic~~~zh-TW~0.0.1.0', 'Language.Basic~~~zu-ZA~0.0.1.0', 'Language.Fonts.Arab~~~und-ARAB~0.0.1.0', 'Language.Fonts.Beng~~~und-BENG~0.0.1.0', 'Language.Fonts.Cans~~~und-CANS~0.0.1.0', 'Language.Fonts.Cher~~~und-CHER~0.0.1.0', 'Language.Fonts.Deva~~~und-DEVA~0.0.1.0', 'Language.Fonts.Ethi~~~und-ETHI~0.0.1.0', 'Language.Fonts.Gujr~~~und-GUJR~0.0.1.0', 'Language.Fonts.Guru~~~und-GURU~0.0.1.0', 'Language.Fonts.Hans~~~und-HANS~0.0.1.0', 'Language.Fonts.Hant~~~und-HANT~0.0.1.0', 'Language.Fonts.Hebr~~~und-HEBR~0.0.1.0', 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0', 'Language.Fonts.Khmr~~~und-KHMR~0.0.1.0', 'Language.Fonts.Knda~~~und-KNDA~0.0.1.0', 'Language.Fonts.Kore~~~und-KORE~0.0.1.0', 'Language.Fonts.Laoo~~~und-LAOO~0.0.1.0', 'Language.Fonts.Mlym~~~und-MLYM~0.0.1.0', 'Language.Fonts.Orya~~~und-ORYA~0.0.1.0', 'Language.Fonts.PanEuropeanSupplementalFonts~~~~0.0.1.0', 'Language.Fonts.Sinh~~~und-SINH~0.0.1.0', 'Language.Fonts.Syrc~~~und-SYRC~0.0.1.0', 'Language.Fonts.Taml~~~und-TAML~0.0.1.0', 'Language.Fonts.Telu~~~und-TELU~0.0.1.0', 'Language.Fonts.Thai~~~und-THAI~0.0.1.0', 'Language.Handwriting~~~af-ZA~0.0.1.0', 'Language.Handwriting~~~bs-LATN-BA~0.0.1.0', 'Language.Handwriting~~~ca-ES~0.0.1.0', 'Language.Handwriting~~~cs-CZ~0.0.1.0', 'Language.Handwriting~~~cy-GB~0.0.1.0', 'Language.Handwriting~~~da-DK~0.0.1.0', 'Language.Handwriting~~~de-DE~0.0.1.0', 'Language.Handwriting~~~el-GR~0.0.1.0', 'Language.Handwriting~~~en-GB~0.0.1.0', 'Language.Handwriting~~~en-US~0.0.1.0', 'Language.Handwriting~~~es-ES~0.0.1.0', 'Language.Handwriting~~~es-MX~0.0.1.0', 'Language.Handwriting~~~eu-ES~0.0.1.0', 'Language.Handwriting~~~fi-FI~0.0.1.0', 'Language.Handwriting~~~fr-FR~0.0.1.0', 'Language.Handwriting~~~ga-IE~0.0.1.0', 'Language.Handwriting~~~gd-GB~0.0.1.0', 'Language.Handwriting~~~gl-ES~0.0.1.0', 'Language.Handwriting~~~hi-IN~0.0.1.0', 'Language.Handwriting~~~hr-HR~0.0.1.0', 'Language.Handwriting~~~id-ID~0.0.1.0', 'Language.Handwriting~~~it-IT~0.0.1.0', 'Language.Handwriting~~~ja-JP~0.0.1.0', 'Language.Handwriting~~~ko-KR~0.0.1.0', 'Language.Handwriting~~~lb-LU~0.0.1.0', 'Language.Handwriting~~~mi-NZ~0.0.1.0', 'Language.Handwriting~~~ms-BN~0.0.1.0', 'Language.Handwriting~~~ms-MY~0.0.1.0', 'Language.Handwriting~~~nb-NO~0.0.1.0', 'Language.Handwriting~~~nl-NL~0.0.1.0', 'Language.Handwriting~~~nn-NO~0.0.1.0', 'Language.Handwriting~~~nso-ZA~0.0.1.0', 'Language.Handwriting~~~pl-PL~0.0.1.0', 'Language.Handwriting~~~pt-BR~0.0.1.0', 'Language.Handwriting~~~pt-PT~0.0.1.0', 'Language.Handwriting~~~rm-CH~0.0.1.0', 'Language.Handwriting~~~ro-RO~0.0.1.0', 'Language.Handwriting~~~ru-RU~0.0.1.0', 'Language.Handwriting~~~rw-RW~0.0.1.0', 'Language.Handwriting~~~sk-SK~0.0.1.0', 'Language.Handwriting~~~sl-SI~0.0.1.0', 'Language.Handwriting~~~sq-AL~0.0.1.0', 'Language.Handwriting~~~sr-CYRL-RS~0.0.1.0', 'Language.Handwriting~~~sr-LATN-RS~0.0.1.0', 'Language.Handwriting~~~sv-SE~0.0.1.0', 'Language.Handwriting~~~sw-KE~0.0.1.0', 'Language.Handwriting~~~tn-ZA~0.0.1.0', 'Language.Handwriting~~~tr-TR~0.0.1.0', 'Language.Handwriting~~~wo-SN~0.0.1.0', 'Language.Handwriting~~~xh-ZA~0.0.1.0', 'Language.Handwriting~~~zh-CN~0.0.1.0', 'Language.Handwriting~~~zh-HK~0.0.1.0', 'Language.Handwriting~~~zh-TW~0.0.1.0', 'Language.Handwriting~~~zu-ZA~0.0.1.0', 'Language.OCR~~~ar-SA~0.0.1.0', 'Language.OCR~~~bg-BG~0.0.1.0', 'Language.OCR~~~bs-LATN-BA~0.0.1.0', 'Language.OCR~~~cs-CZ~0.0.1.0', 'Language.OCR~~~da-DK~0.0.1.0', 'Language.OCR~~~de-DE~0.0.1.0', 'Language.OCR~~~el-GR~0.0.1.0', 'Language.OCR~~~en-GB~0.0.1.0', 'Language.OCR~~~en-US~0.0.1.0', 'Language.OCR~~~es-ES~0.0.1.0', 'Language.OCR~~~es-MX~0.0.1.0', 'Language.OCR~~~fi-FI~0.0.1.0', 'Language.OCR~~~fr-CA~0.0.1.0', 'Language.OCR~~~fr-FR~0.0.1.0', 'Language.OCR~~~hr-HR~0.0.1.0', 'Language.OCR~~~hu-HU~0.0.1.0', 'Language.OCR~~~it-IT~0.0.1.0', 'Language.OCR~~~ja-JP~0.0.1.0', 'Language.OCR~~~ko-KR~0.0.1.0', 'Language.OCR~~~nb-NO~0.0.1.0', 'Language.OCR~~~nl-NL~0.0.1.0', 'Language.OCR~~~pl-PL~0.0.1.0', 'Language.OCR~~~pt-BR~0.0.1.0', 'Language.OCR~~~pt-PT~0.0.1.0', 'Language.OCR~~~ro-RO~0.0.1.0', 'Language.OCR~~~ru-RU~0.0.1.0', 'Language.OCR~~~sk-SK~0.0.1.0', 'Language.OCR~~~sl-SI~0.0.1.0', 'Language.OCR~~~sr-CYRL-RS~0.0.1.0', 'Language.OCR~~~sr-LATN-RS~0.0.1.0', 'Language.OCR~~~sv-SE~0.0.1.0', 'Language.OCR~~~tr-TR~0.0.1.0', 'Language.OCR~~~zh-CN~0.0.1.0', 'Language.OCR~~~zh-HK~0.0.1.0', 'Language.OCR~~~zh-TW~0.0.1.0', 'Language.Speech~~~de-DE~0.0.1.0', 'Language.Speech~~~en-AU~0.0.1.0', 'Language.Speech~~~en-CA~0.0.1.0', 'Language.Speech~~~en-GB~0.0.1.0', 'Language.Speech~~~en-IN~0.0.1.0', 'Language.Speech~~~en-US~0.0.1.0', 'Language.Speech~~~es-ES~0.0.1.0', 'Language.Speech~~~es-MX~0.0.1.0', 'Language.Speech~~~fr-CA~0.0.1.0', 'Language.Speech~~~fr-FR~0.0.1.0', 'Language.Speech~~~it-IT~0.0.1.0', 'Language.Speech~~~ja-JP~0.0.1.0', 'Language.Speech~~~pt-BR~0.0.1.0', 'Language.Speech~~~zh-CN~0.0.1.0', 'Language.Speech~~~zh-HK~0.0.1.0', 'Language.Speech~~~zh-TW~0.0.1.0', 'Language.TextToSpeech~~~ar-EG~0.0.1.0', 'Language.TextToSpeech~~~ar-SA~0.0.1.0', 'Language.TextToSpeech~~~bg-BG~0.0.1.0', 'Language.TextToSpeech~~~ca-ES~0.0.1.0', 'Language.TextToSpeech~~~cs-CZ~0.0.1.0', 'Language.TextToSpeech~~~da-DK~0.0.1.0', 'Language.TextToSpeech~~~de-AT~0.0.1.0', 'Language.TextToSpeech~~~de-CH~0.0.1.0', 'Language.TextToSpeech~~~de-DE~0.0.1.0', 'Language.TextToSpeech~~~el-GR~0.0.1.0', 'Language.TextToSpeech~~~en-AU~0.0.1.0', 'Language.TextToSpeech~~~en-CA~0.0.1.0', 'Language.TextToSpeech~~~en-GB~0.0.1.0', 'Language.TextToSpeech~~~en-IE~0.0.1.0', 'Language.TextToSpeech~~~en-IN~0.0.1.0', 'Language.TextToSpeech~~~en-US~0.0.1.0', 'Language.TextToSpeech~~~es-ES~0.0.1.0', 'Language.TextToSpeech~~~es-MX~0.0.1.0', 'Language.TextToSpeech~~~fi-FI~0.0.1.0', 'Language.TextToSpeech~~~fr-CA~0.0.1.0', 'Language.TextToSpeech~~~fr-CH~0.0.1.0', 'Language.TextToSpeech~~~fr-FR~0.0.1.0', 'Language.TextToSpeech~~~he-IL~0.0.1.0', 'Language.TextToSpeech~~~hi-IN~0.0.1.0', 'Language.TextToSpeech~~~hr-HR~0.0.1.0', 'Language.TextToSpeech~~~hu-HU~0.0.1.0', 'Language.TextToSpeech~~~id-ID~0.0.1.0', 'Language.TextToSpeech~~~it-IT~0.0.1.0', 'Language.TextToSpeech~~~ja-JP~0.0.1.0', 'Language.TextToSpeech~~~ko-KR~0.0.1.0', 'Language.TextToSpeech~~~ms-MY~0.0.1.0', 'Language.TextToSpeech~~~nb-NO~0.0.1.0', 'Language.TextToSpeech~~~nl-BE~0.0.1.0', 'Language.TextToSpeech~~~nl-NL~0.0.1.0', 'Language.TextToSpeech~~~pl-PL~0.0.1.0', 'Language.TextToSpeech~~~pt-BR~0.0.1.0', 'Language.TextToSpeech~~~pt-PT~0.0.1.0', 'Language.TextToSpeech~~~ro-RO~0.0.1.0', 'Language.TextToSpeech~~~ru-RU~0.0.1.0', 'Language.TextToSpeech~~~sk-SK~0.0.1.0', 'Language.TextToSpeech~~~sl-SI~0.0.1.0', 'Language.TextToSpeech~~~sv-SE~0.0.1.0', 'Language.TextToSpeech~~~ta-IN~0.0.1.0', 'Language.TextToSpeech~~~th-TH~0.0.1.0', 'Language.TextToSpeech~~~tr-TR~0.0.1.0', 'Language.TextToSpeech~~~vi-VN~0.0.1.0', 'Language.TextToSpeech~~~zh-CN~0.0.1.0', 'Language.TextToSpeech~~~zh-HK~0.0.1.0', 'Language.TextToSpeech~~~zh-TW~0.0.1.0', 'MathRecognizer~~~~0.0.1.0', 'Media.WindowsMediaPlayer~~~~0.0.12.0', 'Microsoft.Onecore.StorageManagement~~~~0.0.1.0', 'Microsoft.WebDriver~~~~0.0.1.0', 'Microsoft.Windows.StorageManagement~~~~0.0.1.0', 'Msix.PackagingTool.Driver~~~~0.0.1.0', 'NetFX3~~~~' 'OneCoreUAP.OneSync~~~~0.0.1.0', 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0', 'RasCMAK.Client~~~~0.0.1.0', 'RIP.Listener~~~~0.0.1.0', 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0', 'Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0', 'Rsat.CertificateServices.Tools~~~~0.0.1.0', 'Rsat.DHCP.Tools~~~~0.0.1.0', 'Rsat.Dns.Tools~~~~0.0.1.0', 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0', 'Rsat.FileServices.Tools~~~~0.0.1.0', 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0', 'Rsat.IPAM.Client.Tools~~~~0.0.1.0', 'Rsat.LLDP.Tools~~~~0.0.1.0', 'Rsat.NetworkController.Tools~~~~0.0.1.0', 'Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0', 'Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0', 'Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0', 'Rsat.ServerManager.Tools~~~~0.0.1.0', 'Rsat.Shielded.VM.Tools~~~~0.0.1.0', 'Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0', 'Rsat.StorageReplica.Tools~~~~0.0.1.0', 'Rsat.SystemInsights.Management.Tools~~~~0.0.1.0', 'Rsat.VolumeActivation.Tools~~~~0.0.1.0', 'Rsat.WSUS.Tools~~~~0.0.1.0', 'ServerCore.AppCompatibility~~~~0.0.1.0', 'SNMP.Client~~~~0.0.1.0', 'Tools.DeveloperMode.Core~~~~0.0.1.0', 'Tools.DTrace.Platform~~~~0.0.1.0', 'Tools.Graphics.DirectX~~~~0.0.1.0', 'WMI-SNMP-Provider.Client~~~~0.0.1.0', 'XPS.Viewer~~~~0.0.1.0' ) $Win10_2004_FODs = @('Accessibility.Braille~~~~0.0.1.0', 'Analog.Holographic.Desktop~~~~0.0.1.0', 'App.StepsRecorder~~~~0.0.1.0', 'App.Support.QuickAssist~~~~0.0.1.0', 'App.WirelessDisplay.Connect~~~~0.0.1.0', 'Browser.InternetExplorer~~~~0.0.11.0', 'DirectX.Configuration.Database~~~~0.0.1.0', 'Hello.Face.18967~~~~0.0.1.0', 'Hello.Face.Migration.18967~~~~0.0.1.0', 'Language.Basic~~~af-ZA~0.0.1.0', 'Language.Basic~~~ar-SA~0.0.1.0', 'Language.Basic~~~as-IN~0.0.1.0', 'Language.Basic~~~az-LATN-AZ~0.0.1.0', 'Language.Basic~~~ba-RU~0.0.1.0', 'Language.Basic~~~be-BY~0.0.1.0', 'Language.Basic~~~bg-BG~0.0.1.0', 'Language.Basic~~~bn-BD~0.0.1.0', 'Language.Basic~~~bn-IN~0.0.1.0', 'Language.Basic~~~bs-LATN-BA~0.0.1.0', 'Language.Basic~~~ca-ES~0.0.1.0', 'Language.Basic~~~cs-CZ~0.0.1.0', 'Language.Basic~~~cy-GB~0.0.1.0', 'Language.Basic~~~da-DK~0.0.1.0', 'Language.Basic~~~de-CH~0.0.1.0', 'Language.Basic~~~de-DE~0.0.1.0', 'Language.Basic~~~el-GR~0.0.1.0', 'Language.Basic~~~en-AU~0.0.1.0', 'Language.Basic~~~en-CA~0.0.1.0', 'Language.Basic~~~en-GB~0.0.1.0', 'Language.Basic~~~en-IN~0.0.1.0', 'Language.Basic~~~en-US~0.0.1.0', 'Language.Basic~~~es-ES~0.0.1.0', 'Language.Basic~~~es-MX~0.0.1.0', 'Language.Basic~~~es-US~0.0.1.0', 'Language.Basic~~~et-EE~0.0.1.0', 'Language.Basic~~~eu-ES~0.0.1.0', 'Language.Basic~~~fa-IR~0.0.1.0', 'Language.Basic~~~fi-FI~0.0.1.0', 'Language.Basic~~~fil-PH~0.0.1.0', 'Language.Basic~~~fr-BE~0.0.1.0', 'Language.Basic~~~fr-CA~0.0.1.0', 'Language.Basic~~~fr-CH~0.0.1.0', 'Language.Basic~~~fr-FR~0.0.1.0', 'Language.Basic~~~ga-IE~0.0.1.0', 'Language.Basic~~~gd-GB~0.0.1.0', 'Language.Basic~~~gl-ES~0.0.1.0', 'Language.Basic~~~gu-IN~0.0.1.0', 'Language.Basic~~~ha-LATN-NG~0.0.1.0', 'Language.Basic~~~haw-US~0.0.1.0', 'Language.Basic~~~he-IL~0.0.1.0', 'Language.Basic~~~hi-IN~0.0.1.0', 'Language.Basic~~~hr-HR~0.0.1.0', 'Language.Basic~~~hu-HU~0.0.1.0', 'Language.Basic~~~hy-AM~0.0.1.0', 'Language.Basic~~~id-ID~0.0.1.0', 'Language.Basic~~~ig-NG~0.0.1.0', 'Language.Basic~~~is-IS~0.0.1.0', 'Language.Basic~~~it-IT~0.0.1.0', 'Language.Basic~~~ja-JP~0.0.1.0', 'Language.Basic~~~ka-GE~0.0.1.0', 'Language.Basic~~~kk-KZ~0.0.1.0', 'Language.Basic~~~kl-GL~0.0.1.0', 'Language.Basic~~~kn-IN~0.0.1.0', 'Language.Basic~~~ko-KR~0.0.1.0', 'Language.Basic~~~kok-DEVA-IN~0.0.1.0', 'Language.Basic~~~ky-KG~0.0.1.0', 'Language.Basic~~~lb-LU~0.0.1.0', 'Language.Basic~~~lt-LT~0.0.1.0', 'Language.Basic~~~lv-LV~0.0.1.0', 'Language.Basic~~~mi-NZ~0.0.1.0', 'Language.Basic~~~mk-MK~0.0.1.0', 'Language.Basic~~~ml-IN~0.0.1.0', 'Language.Basic~~~mn-MN~0.0.1.0', 'Language.Basic~~~mr-IN~0.0.1.0', 'Language.Basic~~~ms-BN~0.0.1.0', 'Language.Basic~~~ms-MY~0.0.1.0', 'Language.Basic~~~mt-MT~0.0.1.0', 'Language.Basic~~~nb-NO~0.0.1.0', 'Language.Basic~~~ne-NP~0.0.1.0', 'Language.Basic~~~nl-NL~0.0.1.0', 'Language.Basic~~~nn-NO~0.0.1.0', 'Language.Basic~~~nso-ZA~0.0.1.0', 'Language.Basic~~~or-IN~0.0.1.0', 'Language.Basic~~~pa-IN~0.0.1.0', 'Language.Basic~~~pl-PL~0.0.1.0', 'Language.Basic~~~ps-AF~0.0.1.0', 'Language.Basic~~~pt-BR~0.0.1.0', 'Language.Basic~~~pt-PT~0.0.1.0', 'Language.Basic~~~rm-CH~0.0.1.0', 'Language.Basic~~~ro-RO~0.0.1.0', 'Language.Basic~~~ru-RU~0.0.1.0', 'Language.Basic~~~rw-RW~0.0.1.0', 'Language.Basic~~~sah-RU~0.0.1.0', 'Language.Basic~~~si-LK~0.0.1.0', 'Language.Basic~~~sk-SK~0.0.1.0', 'Language.Basic~~~sl-SI~0.0.1.0', 'Language.Basic~~~sq-AL~0.0.1.0', 'Language.Basic~~~sr-CYRL-RS~0.0.1.0', 'Language.Basic~~~sr-LATN-RS~0.0.1.0', 'Language.Basic~~~sv-SE~0.0.1.0', 'Language.Basic~~~sw-KE~0.0.1.0', 'Language.Basic~~~ta-IN~0.0.1.0', 'Language.Basic~~~te-IN~0.0.1.0', 'Language.Basic~~~tg-CYRL-TJ~0.0.1.0', 'Language.Basic~~~th-TH~0.0.1.0', 'Language.Basic~~~tk-TM~0.0.1.0', 'Language.Basic~~~tn-ZA~0.0.1.0', 'Language.Basic~~~tr-TR~0.0.1.0', 'Language.Basic~~~tt-RU~0.0.1.0', 'Language.Basic~~~ug-CN~0.0.1.0', 'Language.Basic~~~uk-UA~0.0.1.0', 'Language.Basic~~~ur-PK~0.0.1.0', 'Language.Basic~~~uz-LATN-UZ~0.0.1.0', 'Language.Basic~~~vi-VN~0.0.1.0', 'Language.Basic~~~wo-SN~0.0.1.0', 'Language.Basic~~~xh-ZA~0.0.1.0', 'Language.Basic~~~yo-NG~0.0.1.0', 'Language.Basic~~~zh-CN~0.0.1.0', 'Language.Basic~~~zh-HK~0.0.1.0', 'Language.Basic~~~zh-TW~0.0.1.0', 'Language.Basic~~~zu-ZA~0.0.1.0', 'Language.Fonts.Arab~~~und-ARAB~0.0.1.0', 'Language.Fonts.Beng~~~und-BENG~0.0.1.0', 'Language.Fonts.Cans~~~und-CANS~0.0.1.0', 'Language.Fonts.Cher~~~und-CHER~0.0.1.0', 'Language.Fonts.Deva~~~und-DEVA~0.0.1.0', 'Language.Fonts.Ethi~~~und-ETHI~0.0.1.0', 'Language.Fonts.Gujr~~~und-GUJR~0.0.1.0', 'Language.Fonts.Guru~~~und-GURU~0.0.1.0', 'Language.Fonts.Hans~~~und-HANS~0.0.1.0', 'Language.Fonts.Hant~~~und-HANT~0.0.1.0', 'Language.Fonts.Hebr~~~und-HEBR~0.0.1.0', 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0', 'Language.Fonts.Khmr~~~und-KHMR~0.0.1.0', 'Language.Fonts.Knda~~~und-KNDA~0.0.1.0', 'Language.Fonts.Kore~~~und-KORE~0.0.1.0', 'Language.Fonts.Laoo~~~und-LAOO~0.0.1.0', 'Language.Fonts.Mlym~~~und-MLYM~0.0.1.0', 'Language.Fonts.Orya~~~und-ORYA~0.0.1.0', 'Language.Fonts.PanEuropeanSupplementalFonts~~~~0.0.1.0', 'Language.Fonts.Sinh~~~und-SINH~0.0.1.0', 'Language.Fonts.Syrc~~~und-SYRC~0.0.1.0', 'Language.Fonts.Taml~~~und-TAML~0.0.1.0', 'Language.Fonts.Telu~~~und-TELU~0.0.1.0', 'Language.Fonts.Thai~~~und-THAI~0.0.1.0', 'Language.Handwriting~~~af-ZA~0.0.1.0', 'Language.Handwriting~~~bs-LATN-BA~0.0.1.0', 'Language.Handwriting~~~ca-ES~0.0.1.0', 'Language.Handwriting~~~cs-CZ~0.0.1.0', 'Language.Handwriting~~~cy-GB~0.0.1.0', 'Language.Handwriting~~~da-DK~0.0.1.0', 'Language.Handwriting~~~de-DE~0.0.1.0', 'Language.Handwriting~~~el-GR~0.0.1.0', 'Language.Handwriting~~~en-GB~0.0.1.0', 'Language.Handwriting~~~en-US~0.0.1.0', 'Language.Handwriting~~~es-ES~0.0.1.0', 'Language.Handwriting~~~es-MX~0.0.1.0', 'Language.Handwriting~~~eu-ES~0.0.1.0', 'Language.Handwriting~~~fi-FI~0.0.1.0', 'Language.Handwriting~~~fr-FR~0.0.1.0', 'Language.Handwriting~~~ga-IE~0.0.1.0', 'Language.Handwriting~~~gd-GB~0.0.1.0', 'Language.Handwriting~~~gl-ES~0.0.1.0', 'Language.Handwriting~~~hi-IN~0.0.1.0', 'Language.Handwriting~~~hr-HR~0.0.1.0', 'Language.Handwriting~~~id-ID~0.0.1.0', 'Language.Handwriting~~~it-IT~0.0.1.0', 'Language.Handwriting~~~ja-JP~0.0.1.0', 'Language.Handwriting~~~ko-KR~0.0.1.0', 'Language.Handwriting~~~lb-LU~0.0.1.0', 'Language.Handwriting~~~mi-NZ~0.0.1.0', 'Language.Handwriting~~~ms-BN~0.0.1.0', 'Language.Handwriting~~~ms-MY~0.0.1.0', 'Language.Handwriting~~~nb-NO~0.0.1.0', 'Language.Handwriting~~~nl-NL~0.0.1.0', 'Language.Handwriting~~~nn-NO~0.0.1.0', 'Language.Handwriting~~~nso-ZA~0.0.1.0', 'Language.Handwriting~~~pl-PL~0.0.1.0', 'Language.Handwriting~~~pt-BR~0.0.1.0', 'Language.Handwriting~~~pt-PT~0.0.1.0', 'Language.Handwriting~~~rm-CH~0.0.1.0', 'Language.Handwriting~~~ro-RO~0.0.1.0', 'Language.Handwriting~~~ru-RU~0.0.1.0', 'Language.Handwriting~~~rw-RW~0.0.1.0', 'Language.Handwriting~~~sk-SK~0.0.1.0', 'Language.Handwriting~~~sl-SI~0.0.1.0', 'Language.Handwriting~~~sq-AL~0.0.1.0', 'Language.Handwriting~~~sr-CYRL-RS~0.0.1.0', 'Language.Handwriting~~~sr-LATN-RS~0.0.1.0', 'Language.Handwriting~~~sv-SE~0.0.1.0', 'Language.Handwriting~~~sw-KE~0.0.1.0', 'Language.Handwriting~~~tn-ZA~0.0.1.0', 'Language.Handwriting~~~tr-TR~0.0.1.0', 'Language.Handwriting~~~wo-SN~0.0.1.0', 'Language.Handwriting~~~xh-ZA~0.0.1.0', 'Language.Handwriting~~~zh-CN~0.0.1.0', 'Language.Handwriting~~~zh-HK~0.0.1.0', 'Language.Handwriting~~~zh-TW~0.0.1.0', 'Language.Handwriting~~~zu-ZA~0.0.1.0', 'Language.OCR~~~ar-SA~0.0.1.0', 'Language.OCR~~~bg-BG~0.0.1.0', 'Language.OCR~~~bs-LATN-BA~0.0.1.0', 'Language.OCR~~~cs-CZ~0.0.1.0', 'Language.OCR~~~da-DK~0.0.1.0', 'Language.OCR~~~de-DE~0.0.1.0', 'Language.OCR~~~el-GR~0.0.1.0', 'Language.OCR~~~en-GB~0.0.1.0', 'Language.OCR~~~en-US~0.0.1.0', 'Language.OCR~~~es-ES~0.0.1.0', 'Language.OCR~~~es-MX~0.0.1.0', 'Language.OCR~~~fi-FI~0.0.1.0', 'Language.OCR~~~fr-CA~0.0.1.0', 'Language.OCR~~~fr-FR~0.0.1.0', 'Language.OCR~~~hr-HR~0.0.1.0', 'Language.OCR~~~hu-HU~0.0.1.0', 'Language.OCR~~~it-IT~0.0.1.0', 'Language.OCR~~~ja-JP~0.0.1.0', 'Language.OCR~~~ko-KR~0.0.1.0', 'Language.OCR~~~nb-NO~0.0.1.0', 'Language.OCR~~~nl-NL~0.0.1.0', 'Language.OCR~~~pl-PL~0.0.1.0', 'Language.OCR~~~pt-BR~0.0.1.0', 'Language.OCR~~~pt-PT~0.0.1.0', 'Language.OCR~~~ro-RO~0.0.1.0', 'Language.OCR~~~ru-RU~0.0.1.0', 'Language.OCR~~~sk-SK~0.0.1.0', 'Language.OCR~~~sl-SI~0.0.1.0', 'Language.OCR~~~sr-CYRL-RS~0.0.1.0', 'Language.OCR~~~sr-LATN-RS~0.0.1.0', 'Language.OCR~~~sv-SE~0.0.1.0', 'Language.OCR~~~tr-TR~0.0.1.0', 'Language.OCR~~~zh-CN~0.0.1.0', 'Language.OCR~~~zh-HK~0.0.1.0', 'Language.OCR~~~zh-TW~0.0.1.0', 'Language.Speech~~~da-DK~0.0.1.0', 'Language.Speech~~~de-DE~0.0.1.0', 'Language.Speech~~~en-AU~0.0.1.0', 'Language.Speech~~~en-CA~0.0.1.0', 'Language.Speech~~~en-GB~0.0.1.0', 'Language.Speech~~~en-IN~0.0.1.0', 'Language.Speech~~~en-US~0.0.1.0', 'Language.Speech~~~es-ES~0.0.1.0', 'Language.Speech~~~es-MX~0.0.1.0', 'Language.Speech~~~fr-CA~0.0.1.0', 'Language.Speech~~~fr-FR~0.0.1.0', 'Language.Speech~~~it-IT~0.0.1.0', 'Language.Speech~~~ja-JP~0.0.1.0', 'Language.Speech~~~pt-BR~0.0.1.0', 'Language.Speech~~~zh-CN~0.0.1.0', 'Language.Speech~~~zh-HK~0.0.1.0', 'Language.Speech~~~zh-TW~0.0.1.0', 'Language.TextToSpeech~~~ar-EG~0.0.1.0', 'Language.TextToSpeech~~~ar-SA~0.0.1.0', 'Language.TextToSpeech~~~bg-BG~0.0.1.0', 'Language.TextToSpeech~~~ca-ES~0.0.1.0', 'Language.TextToSpeech~~~cs-CZ~0.0.1.0', 'Language.TextToSpeech~~~da-DK~0.0.1.0', 'Language.TextToSpeech~~~de-AT~0.0.1.0', 'Language.TextToSpeech~~~de-CH~0.0.1.0', 'Language.TextToSpeech~~~de-DE~0.0.1.0', 'Language.TextToSpeech~~~el-GR~0.0.1.0', 'Language.TextToSpeech~~~en-AU~0.0.1.0', 'Language.TextToSpeech~~~en-CA~0.0.1.0', 'Language.TextToSpeech~~~en-GB~0.0.1.0', 'Language.TextToSpeech~~~en-IE~0.0.1.0', 'Language.TextToSpeech~~~en-IN~0.0.1.0', 'Language.TextToSpeech~~~en-US~0.0.1.0', 'Language.TextToSpeech~~~es-ES~0.0.1.0', 'Language.TextToSpeech~~~es-MX~0.0.1.0', 'Language.TextToSpeech~~~fi-FI~0.0.1.0', 'Language.TextToSpeech~~~fr-CA~0.0.1.0', 'Language.TextToSpeech~~~fr-CH~0.0.1.0', 'Language.TextToSpeech~~~fr-FR~0.0.1.0', 'Language.TextToSpeech~~~he-IL~0.0.1.0', 'Language.TextToSpeech~~~hi-IN~0.0.1.0', 'Language.TextToSpeech~~~hr-HR~0.0.1.0', 'Language.TextToSpeech~~~hu-HU~0.0.1.0', 'Language.TextToSpeech~~~id-ID~0.0.1.0', 'Language.TextToSpeech~~~it-IT~0.0.1.0', 'Language.TextToSpeech~~~ja-JP~0.0.1.0', 'Language.TextToSpeech~~~ko-KR~0.0.1.0', 'Language.TextToSpeech~~~ms-MY~0.0.1.0', 'Language.TextToSpeech~~~nb-NO~0.0.1.0', 'Language.TextToSpeech~~~nl-BE~0.0.1.0', 'Language.TextToSpeech~~~nl-NL~0.0.1.0', 'Language.TextToSpeech~~~pl-PL~0.0.1.0', 'Language.TextToSpeech~~~pt-BR~0.0.1.0', 'Language.TextToSpeech~~~pt-PT~0.0.1.0', 'Language.TextToSpeech~~~ro-RO~0.0.1.0', 'Language.TextToSpeech~~~ru-RU~0.0.1.0', 'Language.TextToSpeech~~~sk-SK~0.0.1.0', 'Language.TextToSpeech~~~sl-SI~0.0.1.0', 'Language.TextToSpeech~~~sv-SE~0.0.1.0', 'Language.TextToSpeech~~~ta-IN~0.0.1.0', 'Language.TextToSpeech~~~th-TH~0.0.1.0', 'Language.TextToSpeech~~~tr-TR~0.0.1.0', 'Language.TextToSpeech~~~vi-VN~0.0.1.0', 'Language.TextToSpeech~~~zh-CN~0.0.1.0', 'Language.TextToSpeech~~~zh-HK~0.0.1.0', 'Language.TextToSpeech~~~zh-TW~0.0.1.0', 'MathRecognizer~~~~0.0.1.0', 'Media.WindowsMediaPlayer~~~~0.0.12.0', 'Microsoft.Onecore.StorageManagement~~~~0.0.1.0', 'Microsoft.WebDriver~~~~0.0.1.0', 'Microsoft.Windows.MSPaint~~~~0.0.1.0', 'Microsoft.Windows.Notepad~~~~0.0.1.0', 'Microsoft.Windows.PowerShell.ISE~~~~0.0.1.0', 'Microsoft.Windows.StorageManagement~~~~0.0.1.0', 'Microsoft.Windows.WordPad~~~~0.0.1.0', 'Msix.PackagingTool.Driver~~~~0.0.1.0', 'NetFX3~~~~', 'Network.Irda~~~~0.0.1.0', 'OneCoreUAP.OneSync~~~~0.0.1.0', 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0', 'Print.Fax.Scan~~~~0.0.1.0', 'Print.Management.Console~~~~0.0.1.0', 'RasCMAK.Client~~~~0.0.1.0', 'RIP.Listener~~~~0.0.1.0', 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0', 'Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0', 'Rsat.CertificateServices.Tools~~~~0.0.1.0', 'Rsat.DHCP.Tools~~~~0.0.1.0', 'Rsat.Dns.Tools~~~~0.0.1.0', 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0', 'Rsat.FileServices.Tools~~~~0.0.1.0', 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0', 'Rsat.IPAM.Client.Tools~~~~0.0.1.0', 'Rsat.LLDP.Tools~~~~0.0.1.0', 'Rsat.NetworkController.Tools~~~~0.0.1.0', 'Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0', 'Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0', 'Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0', 'Rsat.ServerManager.Tools~~~~0.0.1.0', 'Rsat.Shielded.VM.Tools~~~~0.0.1.0', 'Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0', 'Rsat.StorageReplica.Tools~~~~0.0.1.0', 'Rsat.SystemInsights.Management.Tools~~~~0.0.1.0', 'Rsat.VolumeActivation.Tools~~~~0.0.1.0', 'Rsat.WSUS.Tools~~~~0.0.1.0', 'SNMP.Client~~~~0.0.1.0', 'Tools.DeveloperMode.Core~~~~0.0.1.0', 'Tools.Graphics.DirectX~~~~0.0.1.0', 'Windows.Client.ShellComponents~~~~0.0.1.0', 'Windows.Desktop.EMS-SAC.Tools~~~~0.0.1.0', 'WMI-SNMP-Provider.Client~~~~0.0.1.0', 'XPS.Viewer~~~~0.0.1.0') If ($Winver -eq '2004') { $items = ($Win10_2004_FODs | Out-GridView -Title 'Select Features On Demand' -PassThru) } If ($Winver -eq '1909') { $items = ($Win10_1909_FODs | Out-GridView -Title 'Select Features On Demand' -PassThru) } If ($Winver -eq '1903') { $items = ($Win10_1903_FODs | Out-GridView -Title 'Select Features On Demand' -PassThru) } If ($Winver -eq '1809') { if ($WinOS -eq 'Windows 10') { $items = ($Win10_1809_FODs | Out-GridView -Title 'Select Features On Demand' -PassThru) } if ($WinOS -eq 'Windows Server') { $items = ($Win10_1809_server_FODs | Out-GridView -Title 'Select Features On Demand' -PassThru) } } #(Get-ChildItem -path $LPSourceFolder | Select-Object -Property Name | Out-GridView -title "Select Local Experience Packs" -PassThru) if ($WinOS -eq 'Windows 11') { $items = (Get-ChildItem -Path "$global:workdir\imports\fods\Windows 11\$winver" | Select-Object -Property Name | Out-GridView -Title 'Select Featres' -PassThru) foreach ($item in $items) { $WPFCustomLBFOD.Items.Add($item.name) } } else { foreach ($item in $items) { $WPFCustomLBFOD.Items.Add($item) } } } #Function to apply the selected Langauge Packs to the mounted WIM Function Install-LanguagePacks { Update-Log -data 'Applying Language Packs...' -Class Information $WinOS = Get-WindowsType $Winver = Get-WinVersionNumber if (($WinOS -eq 'Windows 10') -and (($winver -eq '20H2') -or ($winver -eq '21H1') -or ($winver -eq '2009') -or ($winver -eq '21H2') -or ($winver -eq '22H2'))) { $winver = '2004' } $mountdir = $WPFMISMountTextBox.text $LPSourceFolder = $global:workdir + '\imports\Lang\' + $WinOS + '\' + $winver + '\LanguagePacks\' $items = $WPFCustomLBLangPacks.items foreach ($item in $items) { $source = $LPSourceFolder + $item $text = 'Applying ' + $item Update-Log -Data $text -Class Information try { if ($demomode -eq $true) { $string = 'Demo mode active - not applying ' + $source Update-Log -data $string -Class Warning } else { Add-WindowsPackage -PackagePath $source -Path $mountdir -ErrorAction Stop | Out-Null Update-Log -Data 'Injection Successful' -Class Information } } catch { Update-Log -Data 'Failed to inject Language Pack' -Class Error Update-Log -data $_.Exception.Message -Class Error } } Update-Log -Data 'Language Pack injections complete' -Class Information } #Function to apply selected LXPs to the mounted WIM Function Install-LocalExperiencePack { Update-Log -data 'Applying Local Experience Packs...' -Class Information $mountdir = $WPFMISMountTextBox.text $WinOS = Get-WindowsType $Winver = Get-WinVersionNumber if (($WinOS -eq 'Windows 10') -and (($winver -eq '20H2') -or ($winver -eq '21H1') -or ($winver -eq '2009') -or ($winver -eq '21H2') -or ($winver -eq '22H2'))) { $winver = '2004' } $LPSourceFolder = $global:workdir + '\imports\Lang\' + $WinOS + '\' + $winver + '\localexperiencepack\' $items = $WPFCustomLBLEP.items foreach ($item in $items) { $source = $LPSourceFolder + $item $license = Get-Item -Path $source\*.xml $file = Get-Item -Path $source\*.appx $text = 'Applying ' + $item Update-Log -Data $text -Class Information try { Add-ProvisionedAppxPackage -PackagePath $file -LicensePath $license -Path $mountdir -ErrorAction Stop | Out-Null Update-Log -Data 'Injection Successful' -Class Information } catch { Update-Log -data 'Failed to apply Local Experience Pack' -Class Error Update-Log -data $_.Exception.Message -Class Error } } Update-Log -Data 'Local Experience Pack injections complete' -Class Information } #Function to apply selected FODs to the mounted WIM Function Install-FeaturesOnDemand { Update-Log -data 'Applying Features On Demand...' -Class Information $mountdir = $WPFMISMountTextBox.text $WinOS = Get-WindowsType $Winver = Get-WinVersionNumber if (($WinOS -eq 'Windows 10') -and (($winver -eq '20H2') -or ($winver -eq '21H1') -or ($winver -eq '2009') -or ($winver -eq '21H2') -or ($winver -eq '22H2'))) { $winver = '2004' } $FODsource = $global:workdir + '\imports\FODs\' + $winOS + '\' + $Winver + '\' $items = $WPFCustomLBFOD.items foreach ($item in $items) { $text = 'Applying ' + $item Update-Log -Data $text -Class Information try { Add-WindowsCapability -Path $mountdir -Name $item -Source $FODsource -ErrorAction Stop | Out-Null Update-Log -Data 'Injection Successful' -Class Information } catch { Update-Log -data 'Failed to apply Feature On Demand' -Class Error Update-Log -data $_.Exception.Message -Class Error } } Update-Log -Data 'Feature on Demand injections complete' -Class Information } #Function to import the selected LP's in to the Imports folder Function Import-LanguagePacks($Winver, $LPSourceFolder, $WinOS) { Update-Log -Data 'Importing Language Packs...' -Class Information #Note To Donna - Make a step that checks if $winver -eq 1903, and if so, set $winver to 1909 if ($winver -eq '1903') { Update-Log -Data 'Changing version variable because 1903 and 1909 use the same packages' -Class Information $winver = '1909' } if ((Test-Path -Path $global:workdir\imports\Lang\$WinOS\$winver\LanguagePacks) -eq $False) { Update-Log -Data 'Destination folder does not exist. Creating...' -Class Warning $path = $global:workdir + '\imports\Lang\' + $WinOS + '\' + $winver + '\LanguagePacks' $text = 'Creating folder ' + $path Update-Log -data $text -Class Information New-Item -Path $global:workdir\imports\Lang\$WinOS\$winver -Name LanguagePacks -ItemType Directory Update-Log -Data 'Folder created successfully' -Class Information } $items = $WPFImportOtherLBList.items foreach ($item in $items) { $source = $LPSourceFolder + $item $text = 'Importing ' + $item Update-Log -Data $text -Class Information Copy-Item $source -Destination $global:workdir\imports\Lang\$WinOS\$Winver\LanguagePacks -Force } Update-Log -Data 'Importation Complete' -Class Information } #Function to import the selected LXP's into the imports forlder Function Import-LocalExperiencePack($Winver, $LPSourceFolder, $WinOS) { if ($winver -eq '1903') { Update-Log -Data 'Changing version variable because 1903 and 1909 use the same packages' -Class Information $winver = '1909' } Update-Log -Data 'Importing Local Experience Packs...' -Class Information if ((Test-Path -Path $global:workdir\imports\Lang\$WinOS\$winver\localexperiencepack) -eq $False) { Update-Log -Data 'Destination folder does not exist. Creating...' -Class Warning $path = $global:workdir + '\imports\Lang\' + $WinOS + '\' + $winver + '\localexperiencepack' $text = 'Creating folder ' + $path Update-Log -data $text -Class Information New-Item -Path $global:workdir\imports\Lang\$WinOS\$winver -Name localexperiencepack -ItemType Directory Update-Log -Data 'Folder created successfully' -Class Information } $items = $WPFImportOtherLBList.items foreach ($item in $items) { $name = $item $source = $LPSourceFolder + $name $text = 'Creating destination folder for ' + $item Update-Log -Data $text -Class Information if ((Test-Path -Path $global:workdir\imports\lang\$WinOS\$winver\localexperiencepack\$name) -eq $False) { New-Item -Path $global:workdir\imports\lang\$WinOS\$winver\localexperiencepack -Name $name -ItemType Directory } else { $text = 'The folder for ' + $item + ' already exists. Skipping creation...' Update-Log -Data $text -Class Warning } Update-Log -Data 'Copying source to destination folders...' -Class Information Get-ChildItem -Path $source | Copy-Item -Destination $global:workdir\imports\Lang\$WinOS\$Winver\LocalExperiencePack\$name -Force } Update-log -Data 'Importation complete' -Class Information } #Function to import the contents of the selected FODs into the imports forlder Function Import-FeatureOnDemand($Winver, $LPSourceFolder, $WinOS) { if ($winver -eq '1903') { Update-Log -Data 'Changing version variable because 1903 and 1909 use the same packages' -Class Information $winver = '1909' } $path = $WPFImportOtherTBPath.text $text = 'Starting importation of Feature On Demand binaries from ' + $path Update-Log -Data $text -Class Information $langpacks = Get-ChildItem -Path $LPSourceFolder if ((Test-Path -Path $global:workdir\imports\FODs\$WinOS\$Winver) -eq $False) { Update-Log -Data 'Destination folder does not exist. Creating...' -Class Warning $path = $global:workdir + '\imports\FODs\' + $WinOS + '\' + $winver $text = 'Creating folder ' + $path Update-Log -data $text -Class Information New-Item -Path $global:workdir\imports\fods\$WinOS -Name $winver -ItemType Directory Update-Log -Data 'Folder created successfully' -Class Information } #If Windows 11 if ($WPFImportOtherCBWinOS.SelectedItem -eq 'Windows 11') { $items = $WPFImportOtherLBList.items foreach ($item in $items) { $source = $LPSourceFolder + $item $text = 'Importing ' + $item Update-Log -Data $text -Class Information Copy-Item $source -Destination $global:workdir\imports\FODs\$WinOS\$Winver\ -Force } } #If not Windows 11 if ($WPFImportOtherCBWinOS.SelectedItem -ne 'Windows 11') { foreach ($langpack in $langpacks) { $source = $LPSourceFolder + $langpack.name Copy-Item $source -Destination $global:workdir\imports\FODs\$WinOS\$Winver\ -Force $name = $langpack.name $text = 'Copying ' + $name Update-Log -Data $text -Class Information } } Update-Log -Data 'Importing metadata subfolder...' -Class Information Get-ChildItem -Path ($LPSourceFolder + '\metadata\') | Copy-Item -Destination $global:workdir\imports\FODs\$WinOS\$Winver\metadata -Force Update-Log -data 'Feature On Demand imporation complete.' } #Function to update winver cobmo box Function Update-ImportVersionCB { $WPFImportOtherCBWinVer.Items.Clear() if ($WPFImportOtherCBWinOS.SelectedItem -eq 'Windows Server') { Foreach ($WinSrvVer in $WinSrvVer) { $WPFImportOtherCBWinVer.Items.Add($WinSrvVer) } } if ($WPFImportOtherCBWinOS.SelectedItem -eq 'Windows 10') { Foreach ($Win10Ver in $Win10ver) { $WPFImportOtherCBWinVer.Items.Add($Win10Ver) } } if ($WPFImportOtherCBWinOS.SelectedItem -eq 'Windows 11') { Foreach ($Win11Ver in $Win11ver) { $WPFImportOtherCBWinVer.Items.Add($Win11Ver) } } } #Function to select other object import source path Function Select-ImportOtherPath { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Source folder' $null = $browser.ShowDialog() $ImportPath = $browser.SelectedPath + '\' $WPFImportOtherTBPath.text = $ImportPath } #Function to allow user to pause MAke it so process Function Suspend-MakeItSo { $MISPause = ([System.Windows.MessageBox]::Show('Click Yes to continue the image build. Click No to cancel and discard the wim file.', 'WIM Witch Paused', 'YesNo', 'Warning')) if ($MISPause -eq 'Yes') { return 'Yes' } if ($MISPause -eq 'No') { return 'No' } } #Function to run a powershell script with supplied paramenters Function Start-Script($file, $parameter) { $string = "$file $parameter" try { Update-Log -Data 'Running script' -Class Information Invoke-Expression -Command $string -ErrorAction Stop Update-Log -data 'Script complete' -Class Information } catch { Update-Log -Data 'Script failed' -Class Error } } #Function to select existing configMgr image package Function Get-ImageInfo { Param( [parameter(mandatory = $true)] [string]$PackID ) #set-ConfigMgrConnection Set-Location $CMDrive $image = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_ImagePackage -ComputerName $global:SiteServer) | Where-Object { ($_.PackageID -eq $PackID) } $WPFCMTBImageName.text = $image.name $WPFCMTBWinBuildNum.text = $image.ImageOSversion $WPFCMTBPackageID.text = $image.PackageID $WPFCMTBImageVer.text = $image.version $WPFCMTBDescription.text = $image.Description $text = 'Image ' + $WPFCMTBImageName.text + ' selected' Update-Log -data $text -class Information $text = 'Package ID is ' + $image.PackageID Update-Log -data $text -class Information $text = 'Image build number is ' + $image.ImageOSversion Update-Log -data $text -class Information $packageID = (Get-CMOperatingSystemImage -Id $image.PackageID) # $packageID.PkgSourcePath $WPFMISWimFolderTextBox.text = (Split-Path -Path $packageID.PkgSourcePath) $WPFMISWimNameTextBox.text = (Split-Path -Path $packageID.PkgSourcePath -Leaf) $Package = $packageID.PackageID $DPs = Get-CMDistributionPoint $NALPaths = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -ComputerName $global:SiteServer -Query "SELECT * FROM SMS_DistributionPoint WHERE PackageID='$Package'") Update-Log -Data 'Retrieving Distrbution Point Information' -Class Information foreach ($NALPath in $NALPaths) { foreach ($dp in $dps) { $DPPath = $dp.NetworkOSPath if ($NALPath.ServerNALPath -like ("*$DPPath*")) { Update-Log -data "Image has been previously distributed to $DPPath" -class Information $WPFCMLBDPs.Items.Add($DPPath) } } } #Detect Binary Diff Replication Update-Log -data 'Checking Binary Differential Replication setting' -Class Information if ($image.PkgFlags -eq ($image.PkgFlags -bor 0x04000000)) { $WPFCMCBBinDirRep.IsChecked = $True } else { $WPFCMCBBinDirRep.IsChecked = $False } #Detect Package Share Enabled Update-Log -data 'Checking package share settings' -Class Information if ($image.PkgFlags -eq ($image.PkgFlags -bor 0x80)) { $WPFCMCBDeploymentShare.IsChecked = $true } else { $WPFCMCBDeploymentShare.IsChecked = $false } Set-Location $global:workdir } #Function to select DP's from ConfigMgr Function Select-DistributionPoints { #set-ConfigMgrConnection Set-Location $CMDrive if ($WPFCMCBDPDPG.SelectedItem -eq 'Distribution Points') { $SelectedDPs = (Get-CMDistributionPoint -SiteCode $global:sitecode).NetworkOSPath | Out-GridView -Title 'Select Distribution Points' -PassThru foreach ($SelectedDP in $SelectedDPs) { $WPFCMLBDPs.Items.Add($SelectedDP) } } if ($WPFCMCBDPDPG.SelectedItem -eq 'Distribution Point Groups') { $SelectedDPs = (Get-CMDistributionPointGroup).Name | Out-GridView -Title 'Select Distribution Point Groups' -PassThru foreach ($SelectedDP in $SelectedDPs) { $WPFCMLBDPs.Items.Add($SelectedDP) } } Set-Location $global:workdir } #Function to create the new image in ConfigMgr Function New-CMImagePackage { #set-ConfigMgrConnection Set-Location $CMDrive $Path = $WPFMISWimFolderTextBox.text + '\' + $WPFMISWimNameTextBox.text try { New-CMOperatingSystemImage -Name $WPFCMTBImageName.text -Path $Path -ErrorAction Stop Update-Log -data 'Image was created. Check ConfigMgr console' -Class Information } catch { Update-Log -data 'Failed to create the image' -Class Error Update-Log -data $_.Exception.Message -Class Error } $PackageID = (Get-CMOperatingSystemImage -Name $WPFCMTBImageName.text).PackageID Update-Log -Data "The Package ID of the new image is $PackageID" -Class Information Set-ImageProperties -PackageID $PackageID Update-Log -Data 'Retriveing Distribution Point information...' -Class Information $DPs = $WPFCMLBDPs.Items foreach ($DP in $DPs) { # Hello! This line was written on 3/3/2020. $DP = $DP -replace '\\', '' Update-Log -Data 'Distributiong image package content...' -Class Information if ($WPFCMCBDPDPG.SelectedItem -eq 'Distribution Points') { Start-CMContentDistribution -OperatingSystemImageId $PackageID -DistributionPointName $DP } if ($WPFCMCBDPDPG.SelectedItem -eq 'Distribution Point Groups') { Start-CMContentDistribution -OperatingSystemImageId $PackageID -DistributionPointGroupName $DP } Update-Log -Data 'Content has been distributed.' -Class Information } Save-Configuration -CM $PackageID Set-Location $global:workdir } #Function to enable/disable options on ConfigMgr tab Function Enable-ConfigMgrOptions { #"Disabled","New Image","Update Existing Image" if ($WPFCMCBImageType.SelectedItem -eq 'New Image') { $WPFCMBAddDP.IsEnabled = $True $WPFCMBRemoveDP.IsEnabled = $True $WPFCMBSelectImage.IsEnabled = $False $WPFCMCBBinDirRep.IsEnabled = $True $WPFCMCBDPDPG.IsEnabled = $True $WPFCMLBDPs.IsEnabled = $True $WPFCMTBDescription.IsEnabled = $True $WPFCMTBImageName.IsEnabled = $True $WPFCMTBImageVer.IsEnabled = $True $WPFCMTBPackageID.IsEnabled = $False # $WPFCMTBSitecode.IsEnabled = $True # $WPFCMTBSiteServer.IsEnabled = $True $WPFCMTBWinBuildNum.IsEnabled = $False $WPFCMCBImageVerAuto.IsEnabled = $True $WPFCMCBDescriptionAuto.IsEnabled = $True $WPFCMCBDeploymentShare.IsEnabled = $True # $MEMCMsiteinfo = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification" # $WPFCMTBSiteServer.text = $MEMCMsiteinfo.'Site Server' # $WPFCMTBSitecode.text = $MEMCMsiteinfo.'Site Code' Update-Log -data 'ConfigMgr feature enabled. New Image selected' -class Information # Update-Log -data $WPFCMTBSitecode.text -class Information # Update-Log -data $WPFCMTBSiteServer.text -class Information } if ($WPFCMCBImageType.SelectedItem -eq 'Update Existing Image') { $WPFCMBAddDP.IsEnabled = $False $WPFCMBRemoveDP.IsEnabled = $False $WPFCMBSelectImage.IsEnabled = $True $WPFCMCBBinDirRep.IsEnabled = $True $WPFCMCBDPDPG.IsEnabled = $False $WPFCMLBDPs.IsEnabled = $False $WPFCMTBDescription.IsEnabled = $True $WPFCMTBImageName.IsEnabled = $False $WPFCMTBImageVer.IsEnabled = $True $WPFCMTBPackageID.IsEnabled = $True $WPFCMTBSitecode.IsEnabled = $True $WPFCMTBSiteServer.IsEnabled = $True $WPFCMTBWinBuildNum.IsEnabled = $False $WPFCMCBImageVerAuto.IsEnabled = $True $WPFCMCBDescriptionAuto.IsEnabled = $True $WPFCMCBDeploymentShare.IsEnabled = $True # $MEMCMsiteinfo = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification" # $WPFCMTBSiteServer.text = $MEMCMsiteinfo.'Site Server' # $WPFCMTBSitecode.text = $MEMCMsiteinfo.'Site Code' Update-Log -data 'ConfigMgr feature enabled. Update an existing image selected' -class Information # Update-Log -data $WPFCMTBSitecode.text -class Information # Update-Log -data $WPFCMTBSiteServer.text -class Information } if ($WPFCMCBImageType.SelectedItem -eq 'Disabled') { $WPFCMBAddDP.IsEnabled = $False $WPFCMBRemoveDP.IsEnabled = $False $WPFCMBSelectImage.IsEnabled = $False $WPFCMCBBinDirRep.IsEnabled = $False $WPFCMCBDPDPG.IsEnabled = $False $WPFCMLBDPs.IsEnabled = $False $WPFCMTBDescription.IsEnabled = $False $WPFCMTBImageName.IsEnabled = $False $WPFCMTBImageVer.IsEnabled = $False $WPFCMTBPackageID.IsEnabled = $False # $WPFCMTBSitecode.IsEnabled = $False # $WPFCMTBSiteServer.IsEnabled = $False $WPFCMTBWinBuildNum.IsEnabled = $False $WPFCMCBImageVerAuto.IsEnabled = $False $WPFCMCBDescriptionAuto.IsEnabled = $False $WPFCMCBDeploymentShare.IsEnabled = $False Update-Log -data 'ConfigMgr feature disabled' -class Information } } #Function to update DP's when updating existing image file in ConfigMgr Function Update-CMImage { #set-ConfigMgrConnection Set-Location $CMDrive $wmi = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_ImagePackage -ComputerName $global:SiteServer) | Where-Object { $_.PackageID -eq $WPFCMTBPackageID.text } Update-Log -Data 'Updating images on the Distribution Points...' $WMI.RefreshPkgSource() | Out-Null Update-Log -Data 'Refreshing image proprties from the WIM' -Class Information $WMI.ReloadImageProperties() | Out-Null Set-ImageProperties -PackageID $WPFCMTBPackageID.Text Save-Configuration -CM -filename $WPFCMTBPackageID.Text Set-Location $global:workdir } #Function to enable disable & options on the Software Update Catalog tab Function Invoke-UpdateTabOptions { if ($WPFUSCBSelectCatalogSource.SelectedItem -eq 'None' ) { $WPFUpdateOSDBUpdateButton.IsEnabled = $false $WPFUpdatesDownloadNewButton.IsEnabled = $false $WPFUpdatesW10Main.IsEnabled = $false $WPFUpdatesS2019.IsEnabled = $false $WPFUpdatesS2016.IsEnabled = $false $WPFMISCBCheckForUpdates.IsEnabled = $false $WPFMISCBCheckForUpdates.IsChecked = $false } if ($WPFUSCBSelectCatalogSource.SelectedItem -eq 'OSDSUS') { $WPFUpdateOSDBUpdateButton.IsEnabled = $true $WPFUpdatesDownloadNewButton.IsEnabled = $true $WPFUpdatesW10Main.IsEnabled = $true $WPFUpdatesS2019.IsEnabled = $true $WPFUpdatesS2016.IsEnabled = $true $WPFMISCBCheckForUpdates.IsEnabled = $false $WPFMISCBCheckForUpdates.IsChecked = $false Update-Log -data 'OSDSUS selected as update catalog' -class Information Invoke-OSDCheck } if ($WPFUSCBSelectCatalogSource.SelectedItem -eq 'ConfigMgr') { $WPFUpdateOSDBUpdateButton.IsEnabled = $false $WPFUpdatesDownloadNewButton.IsEnabled = $true $WPFUpdatesW10Main.IsEnabled = $true $WPFUpdatesS2019.IsEnabled = $true $WPFUpdatesS2016.IsEnabled = $true $WPFMISCBCheckForUpdates.IsEnabled = $true # $MEMCMsiteinfo = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification" # $WPFCMTBSiteServer.text = $MEMCMsiteinfo.'Site Server' # $WPFCMTBSitecode.text = $MEMCMsiteinfo.'Site Code' Update-Log -data 'ConfigMgr is selected as the update catalog' -Class Information } } Function Invoke-MSUpdateItemDownload { [CmdletBinding(SupportsShouldProcess = $true)] param( [parameter(Mandatory = $true, HelpMessage = 'Specify the path to where the update item will be downloaded.')] [ValidateNotNullOrEmpty()] [string]$FilePath, $UpdateName ) #write-host $updatename #write-host $filepath $OptionalUpdateCheck = 0 #Adding in optional updates if ($UpdateName -like '*Adobe*') { $UpdateClass = 'AdobeSU' $OptionalUpdateCheck = 1 } if ($UpdateName -like '*Microsoft .NET Framework*') { $UpdateClass = 'DotNet' $OptionalUpdateCheck = 1 } if ($UpdateName -like '*Cumulative Update for .NET Framework*') { $OptionalUpdateCheck = 1 $UpdateClass = 'DotNetCU' } if ($UpdateName -like '*Cumulative Update for Windows*') { $UpdateClass = 'LCU' $OptionalUpdateCheck = 1 } if ($UpdateName -like '*Cumulative Update for Microsoft*') { $UpdateClass = 'LCU' $OptionalUpdateCheck = 1 } if ($UpdateName -like '*Servicing Stack Update*') { $OptionalUpdateCheck = 1 $UpdateClass = 'SSU' } if ($UpdateName -like '*Dynamic*') { $OptionalUpdateCheck = 1 $UpdateClass = 'Dynamic' } if ($OptionalUpdateCheck -eq '0') { #Update-Log -data "This update appears to be optional. Skipping..." -Class Warning #return if ($WPFUpdatesCBEnableOptional.IsChecked -eq $True) { Update-Log -data 'This update appears to be optional. Downloading...' -Class Information } else { Update-Log -data 'This update appears to be optional, but are not enabled for download. Skipping...' -Class Information return } #Update-Log -data "This update appears to be optional. Downloading..." -Class Information $UpdateClass = 'Optional' } if ($UpdateName -like '*Windows 10*') { #here #if (($UpdateName -like "* 1903 *") -or ($UpdateName -like "* 1909 *") -or ($UpdateName -like "* 2004 *") -or ($UpdateName -like "* 20H2 *") -or ($UpdateName -like "* 21H1 *") -or ($UpdateName -like "* 21H2 *") -or ($UpdateName -like "* 22H2 *")){$WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'"} if (($UpdateName -like '* 1903 *') -or ($UpdateName -like '* 1909 *') -or ($UpdateName -like '* 2004 *') -or ($UpdateName -like '* 20H2 *') -or ($UpdateName -like '* 21H1 *') -or ($UpdateName -like '* 21H2 *') -or ($UpdateName -like '* 22H2 *')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'" } else { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10'" } if ($updateName -like '*Dynamic*') { if ($WPFUpdatesCBEnableDynamic.IsChecked -eq $True) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10 Dynamic Update'" } } #else{ #Update-Log -data "Dynamic updates have not been selected for downloading. Skipping..." -Class Information #return #} } if ($UpdateName -like '*Windows 11*') { { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 11'" } if ($updateName -like '*Dynamic*') { if ($WPFUpdatesCBEnableDynamic.IsChecked -eq $True) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 11 Dynamic Update'" } } } if (($UpdateName -like '*Windows Server*') -and ($ver -eq '1607')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2016'" } if (($UpdateName -like '*Windows Server*') -and ($ver -eq '1809')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2019'" } if (($UpdateName -like '*Windows Server*') -and ($ver -eq '21H2')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Microsoft Server operating system-21H2'" } $UpdateItem = Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.LocalizedDisplayName -eq $UpdateName) } if ($null -ne $UpdateItem) { # Determine the ContentID instances associated with the update instance $UpdateItemContentIDs = Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_CIToContent -ComputerName $global:SiteServer -Filter "CI_ID = $($UpdateItem.CI_ID)" -ErrorAction Stop if ($null -ne $UpdateItemContentIDs) { # Account for multiple content ID items foreach ($UpdateItemContentID in $UpdateItemContentIDs) { # Get the content files associated with current Content ID $UpdateItemContent = Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_CIContentFiles -ComputerName $global:SiteServer -Filter "ContentID = $($UpdateItemContentID.ContentID)" -ErrorAction Stop if ($null -ne $UpdateItemContent) { # Create new custom object for the update content #write-host $UpdateItemContent.filename $PSObject = [PSCustomObject]@{ 'DisplayName' = $UpdateItem.LocalizedDisplayName 'ArticleID' = $UpdateItem.ArticleID 'FileName' = $UpdateItemContent.filename 'SourceURL' = $UpdateItemContent.SourceURL 'DateRevised' = [System.Management.ManagementDateTimeConverter]::ToDateTime($UpdateItem.DateRevised) } $variable = $FilePath + $UpdateClass + '\' + $UpdateName if ((Test-Path -Path $variable) -eq $false) { Update-Log -Data "Creating folder $variable" -Class Information New-Item -Path $variable -ItemType Directory | Out-Null Update-Log -data 'Created folder' -Class Information } else { $testpath = $variable + '\' + $PSObject.FileName if ((Test-Path -Path $testpath) -eq $true) { Update-Log -Data 'Update already exists. Skipping the download...' -Class Information return } } try { Update-Log -Data "Downloading update item content from: $($PSObject.SourceURL)" -Class Information $DNLDPath = $variable + '\' + $PSObject.FileName $WebClient = New-Object -TypeName System.Net.WebClient $WebClient.DownloadFile($PSObject.SourceURL, $DNLDPath) Update-Log -Data "Download completed successfully, renamed file to: $($PSObject.FileName)" -Class Information $ReturnValue = 0 } catch [System.Exception] { Update-Log -data "Unable to download update item content. Error message: $($_.Exception.Message)" -Class Error $ReturnValue = 1 } } else { Update-Log -data "Unable to determine update content instance for CI_ID: $($UpdateItemContentID.ContentID)" -Class Error $ReturnValue = 1 } } } else { Update-Log -Data "Unable to determine ContentID instance for CI_ID: $($UpdateItem.CI_ID)" -Class Error $ReturnValue = 1 } } else { Update-Log -data "Unable to locate update item from SMS Provider for update type: $($UpdateType)" -Class Error $ReturnValue = 2 } # Handle return value from Function return $ReturnValue | Out-Null } #Function to check for updates against ConfigMgr Function Invoke-MEMCMUpdatecatalog($prod, $ver) { #set-ConfigMgrConnection Set-Location $CMDrive $Arch = 'x64' if ($prod -eq 'Windows 10') { # if (($ver -ge '1903') -or ($ver -eq "21H1")){$WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'"} # if (($ver -ge '1903') -or ($ver -eq "21H1") -or ($ver -eq "20H2") -or ($ver -eq "21H2") -or ($ver -eq "22H2")){$WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'"} #here if (($ver -ge '1903') -or ($ver -like '2*')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'" } if ($ver -le '1809') { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10'" } $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($ver)*$($Arch)*") } ) } if (($prod -like '*Windows Server*') -and ($ver -eq '1607')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2016'" $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -notlike '* Next *') -and ($_.LocalizedDisplayName -notlike '*(1703)*') -and ($_.LocalizedDisplayName -notlike '*(1709)*') -and ($_.LocalizedDisplayName -notlike '*(1803)*') }) } if (($prod -like '*Windows Server*') -and ($ver -eq '1809')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2019'" $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($Arch)*") } ) } if (($prod -like '*Windows Server*') -and ($ver -eq '21H2')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Microsoft Server operating system-21H2'" $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($Arch)*") } ) } if ($prod -eq 'Windows 11') { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 11'" #$Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($Arch)*") } ) if ($ver -eq '21H2') { $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*Windows 11 for $($Arch)*") } ) } else { $Updates = (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($ver)*$($Arch)*") } ) } } if ($WPFUpdatesCBEnableDynamic.IsChecked -eq $True) { if ($prod -eq 'Windows 10') { $Updates = $Updates + (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter "LocalizedCategoryInstanceNames = 'Windows 10 Dynamic Update'" -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$($ver)*$($Arch)*") } ) } if ($prod -eq 'Windows 11') { $Updates = $Updates + (Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter "LocalizedCategoryInstanceNames = 'Windows 11 Dynamic Update'" -ErrorAction Stop | Where-Object { ($_.IsSuperseded -eq $false) -and ($_.LocalizedDisplayName -like "*$prod*") -and ($_.LocalizedDisplayName -like "*$arch*") } ) } } if ($null -eq $updates) { Update-Log -data 'No updates found. Product is likely not synchronized. Continuing with build...' -class Warning Set-Location $global:workdir return } foreach ($update in $updates) { if ((($update.localizeddisplayname -notlike 'Feature update*') -and ($update.localizeddisplayname -notlike 'Upgrade to Windows 11*' )) -and ($update.localizeddisplayname -notlike '*Language Pack*') -and ($update.localizeddisplayname -notlike '*editions),*')) { Update-Log -Data 'Checking the following update:' -Class Information Update-Log -data $update.localizeddisplayname -Class Information #write-host "Display Name" #write-host $update.LocalizedDisplayName # if ($ver -eq "20H2"){$ver = "2009"} #Another 20H2 naming work around Invoke-MSUpdateItemDownload -FilePath "$global:workdir\updates\$Prod\$ver\" -UpdateName $update.LocalizedDisplayName } } Set-Location $global:workdir } #Function to check for supersedence against ConfigMgr Function Invoke-MEMCMUpdateSupersedence($prod, $Ver) { #set-ConfigMgrConnection Set-Location $CMDrive $Arch = 'x64' if (($prod -eq 'Windows 10') -and (($ver -ge '1903') -or ($ver -eq '20H2') -or ($ver -eq '21H1') -or ($ver -eq '21H2') )) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10, version 1903 and later'" } if (($prod -eq 'Windows 10') -and ($ver -le '1809')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows 10'" } if (($prod -eq 'Windows Server') -and ($ver = '1607')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2016'" } if (($prod -eq 'Windows Server') -and ($ver -eq '1809')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Windows Server 2019'" } if (($prod -eq 'Windows Server') -and ($ver -eq '21H2')) { $WMIQueryFilter = "LocalizedCategoryInstanceNames = 'Microsoft Server operating system-21H2'" } Update-Log -data 'Checking files for supersedense...' -Class Information if ((Test-Path -Path "$global:workdir\updates\$Prod\$ver\") -eq $False) { Update-Log -Data 'Folder doesnt exist. Skipping supersedence check...' -Class Warning return } #For every folder under updates\prod\ver $FolderFirstLevels = Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\" foreach ($FolderFirstLevel in $FolderFirstLevels) { #For every folder under updates\prod\ver\class $FolderSecondLevels = Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel" foreach ($FolderSecondLevel in $FolderSecondLevels) { #for every cab under updates\prod\ver\class\update $UpdateCabs = (Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel\$FolderSecondLevel") foreach ($UpdateCab in $UpdateCabs) { Update-Log -data "Checking update file name $UpdateCab" -Class Information $UpdateItem = Get-WmiObject -Namespace "root\SMS\Site_$($global:SiteCode)" -Class SMS_SoftwareUpdate -ComputerName $global:SiteServer -Filter $WMIQueryFilter -ErrorAction Stop | Where-Object { ($_.LocalizedDisplayName -eq $FolderSecondLevel) } if ($UpdateItem.IsSuperseded -eq $false) { Update-Log -data "Update $FolderSecondLevel is current" -Class Information } else { Update-Log -Data "Update $UpdateCab is superseded. Deleting file..." -Class Warning Remove-Item -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel\$FolderSecondLevel\$UpdateCab" } } } } Update-Log -Data 'Cleaning folders...' -Class Information $FolderFirstLevels = Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\" foreach ($FolderFirstLevel in $FolderFirstLevels) { #For every folder under updates\prod\ver\class $FolderSecondLevels = Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel" foreach ($FolderSecondLevel in $FolderSecondLevels) { #for every cab under updates\prod\ver\class\update $UpdateCabs = (Get-ChildItem -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel\$FolderSecondLevel") if ($null -eq $UpdateCabs) { Update-Log -Data "$FolderSecondLevel is empty. Deleting...." -Class Warning Remove-Item -Path "$global:workdir\updates\$Prod\$ver\$FolderFirstLevel\$FolderSecondLevel" } } } Set-Location $global:workdir Update-Log -data 'Supersedence check complete' -class Information } #Function to update source from ConfigMgr when Making It So Function Invoke-MISUpdates { $OS = get-Windowstype $ver = Get-WinVersionNumber if ($ver -eq '2009') { $ver = '20H2' } Invoke-MEMCMUpdateSupersedence -prod $OS -Ver $ver Invoke-MEMCMUpdatecatalog -prod $OS -ver $ver #fucking 2009 to 20h2 } #Function to run the osdsus and osdupdate update check Functions Function Invoke-OSDCheck { Get-OSDBInstallation #Sets OSDUpate version info Get-OSDBCurrentVer #Discovers current version of OSDUpdate Compare-OSDBuilderVer #determines if an update of OSDUpdate can be applied get-osdsusinstallation #Sets OSDSUS version info Get-OSDSUSCurrentVer #Discovers current version of OSDSUS Compare-OSDSUSVer #determines if an update of OSDSUS can be applied } #Function to update image version, properties, and binary delta replication Function Set-ImageProperties($PackageID) { #write-host $PackageID #set-ConfigMgrConnection Set-Location $CMDrive #Version Text Box if ($WPFCMCBImageVerAuto.IsChecked -eq $true) { $string = 'Built ' + (Get-Date -DisplayHint Date) Update-Log -Data "Updating image version to $string" -Class Information Set-CMOperatingSystemImage -Id $PackageID -Version $string } if ($WPFCMCBImageVerAuto.IsChecked -eq $false) { if ($null -ne $WPFCMTBImageVer.text) { Update-Log -Data 'Updating version of the image...' -Class Information Set-CMOperatingSystemImage -Id $PackageID -Version $WPFCMTBImageVer.text } } #Description Text Box if ($WPFCMCBDescriptionAuto.IsChecked -eq $true) { $string = 'This image contains the following customizations: ' if ($WPFUpdatesEnableCheckBox.IsChecked -eq $true) { $string = $string + 'Software Updates, ' } if ($WPFCustomCBLangPacks.IsChecked -eq $true) { $string = $string + 'Language Packs, ' } if ($WPFCustomCBLEP.IsChecked -eq $true) { $string = $string + 'Local Experience Packs, ' } if ($WPFCustomCBFOD.IsChecked -eq $true) { $string = $string + 'Features on Demand, ' } if ($WPFMISDotNetCheckBox.IsChecked -eq $true) { $string = $string + '.Net 3.5, ' } if ($WPFMISOneDriveCheckBox.IsChecked -eq $true) { $string = $string + 'OneDrive Consumer, ' } if ($WPFAppxCheckBox.IsChecked -eq $true) { $string = $string + 'APPX Removal, ' } if ($WPFDriverCheckBox.IsChecked -eq $true) { $string = $string + 'Drivers, ' } if ($WPFJSONEnableCheckBox.IsChecked -eq $true) { $string = $string + 'Autopilot, ' } if ($WPFCustomCBRunScript.IsChecked -eq $true) { $string = $string + 'Custom Script, ' } Update-Log -data 'Setting image description...' -Class Information Set-CMOperatingSystemImage -Id $PackageID -Description $string } if ($WPFCMCBDescriptionAuto.IsChecked -eq $false) { if ($null -ne $WPFCMTBDescription.Text) { Update-Log -Data 'Updating description of the image...' -Class Information Set-CMOperatingSystemImage -Id $PackageID -Description $WPFCMTBDescription.Text } } #Check Box properties #Binary Differnential Replication if ($WPFCMCBBinDirRep.IsChecked -eq $true) { Update-Log -Data 'Enabling Binary Differential Replication' -Class Information Set-CMOperatingSystemImage -Id $PackageID -EnableBinaryDeltaReplication $true } else { Update-Log -Data 'Disabling Binary Differential Replication' -Class Information Set-CMOperatingSystemImage -Id $PackageID -EnableBinaryDeltaReplication $false } #Package Share if ($WPFCMCBDeploymentShare.IsChecked -eq $true) { Update-Log -Data 'Enabling Package Share' -Class Information Set-CMOperatingSystemImage -Id $PackageID -CopyToPackageShareOnDistributionPoint $true } else { Update-Log -Data 'Disabling Package Share' -Class Information Set-CMOperatingSystemImage -Id $PackageID -CopyToPackageShareOnDistributionPoint $false } } #Function to detect and set CM site properties Function Find-ConfigManager() { If ((Test-Path -Path HKLM:\SOFTWARE\Microsoft\SMS\Identification) -eq $true) { Update-Log -Data 'Site Information found in Registry' -Class Information try { $MEMCMsiteinfo = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification' -ErrorAction Stop $WPFCMTBSiteServer.text = $MEMCMsiteinfo.'Site Server' $WPFCMTBSitecode.text = $MEMCMsiteinfo.'Site Code' #$WPFCMTBSiteServer.text = "nt-tpmemcm.notorious.local" #$WPFCMTBSitecode.text = "NTP" $global:SiteCode = $WPFCMTBSitecode.text $global:SiteServer = $WPFCMTBSiteServer.Text $global:CMDrive = $WPFCMTBSitecode.text + ':' Update-Log -Data 'ConfigMgr detected and properties set' -Class Information Update-Log -Data 'ConfigMgr feature enabled' -Class Information $sitecodetext = 'Site Code - ' + $WPFCMTBSitecode.text Update-Log -Data $sitecodetext -Class Information $siteservertext = 'Site Server - ' + $WPFCMTBSiteServer.text Update-Log -Data $siteservertext -Class Information if ($CM -eq 'New') { $WPFCMCBImageType.SelectedIndex = 1 Enable-ConfigMgrOptions } return 0 } catch { Update-Log -Data 'ConfigMgr not detected' -Class Information $WPFCMTBSiteServer.text = 'Not Detected' $WPFCMTBSitecode.text = 'Not Detected' return 1 } } if ((Test-Path -Path $global:workdir\ConfigMgr\SiteInfo.XML) -eq $true) { Update-Log -data 'ConfigMgr Site info XML found' -class Information $settings = Import-Clixml -Path $global:workdir\ConfigMgr\SiteInfo.xml -ErrorAction Stop $WPFCMTBSitecode.text = $settings.SiteCode $WPFCMTBSiteServer.text = $settings.SiteServer Update-Log -Data 'ConfigMgr detected and properties set' -Class Information Update-Log -Data 'ConfigMgr feature enabled' -Class Information $sitecodetext = 'Site Code - ' + $WPFCMTBSitecode.text Update-Log -Data $sitecodetext -Class Information $siteservertext = 'Site Server - ' + $WPFCMTBSiteServer.text Update-Log -Data $siteservertext -Class Information $global:SiteCode = $WPFCMTBSitecode.text $global:SiteServer = $WPFCMTBSiteServer.Text $global:CMDrive = $WPFCMTBSitecode.text + ':' return 0 } Update-Log -Data 'ConfigMgr not detected' -Class Information $WPFCMTBSiteServer.text = 'Not Detected' $WPFCMTBSitecode.text = 'Not Detected' Return 1 } #Function to manually set the CM site properties Function Set-ConfigMgr() { try { # $MEMCMsiteinfo = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SMS\Identification" -ErrorAction Stop # $WPFCMTBSiteServer.text = $MEMCMsiteinfo.'Site Server' # $WPFCMTBSitecode.text = $MEMCMsiteinfo.'Site Code' #$WPFCMTBSiteServer.text = "nt-tpmemcm.notorious.local" #$WPFCMTBSitecode.text = "NTP" $global:SiteCode = $WPFCMTBSitecode.text $global:SiteServer = $WPFCMTBSiteServer.Text $global:CMDrive = $WPFCMTBSitecode.text + ':' Update-Log -Data 'ConfigMgr detected and properties set' -Class Information Update-Log -Data 'ConfigMgr feature enabled' -Class Information $sitecodetext = 'Site Code - ' + $WPFCMTBSitecode.text Update-Log -Data $sitecodetext -Class Information $siteservertext = 'Site Server - ' + $WPFCMTBSiteServer.text Update-Log -Data $siteservertext -Class Information $CMConfig = @{ SiteCode = $WPFCMTBSitecode.text SiteServer = $WPFCMTBSiteServer.text } Update-Log -data 'Saving ConfigMgr site information...' $CMConfig | Export-Clixml -Path $global:workdir\ConfigMgr\SiteInfo.xml -ErrorAction Stop if ($CM -eq 'New') { $WPFCMCBImageType.SelectedIndex = 1 Enable-ConfigMgrOptions } return 0 } catch { Update-Log -Data 'ConfigMgr not detected' -Class Information $WPFCMTBSiteServer.text = 'Not Detected' $WPFCMTBSitecode.text = 'Not Detected' return 1 } } #Function to detect and import CM PowerShell module Function Import-CMModule() { try { $path = (($env:SMS_ADMIN_UI_PATH -replace 'i386', '') + 'ConfigurationManager.psd1') # $path = "C:\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\bin\ConfigurationManager.psd1" Import-Module $path -ErrorAction Stop Update-Log -Data 'ConfigMgr PowerShell module imported' -Class Information return 0 } catch { Update-Log -Data 'Could not import CM PowerShell module.' -Class Warning return 1 } } #Function to apply the start menu layout Function Install-StartLayout { try { $startpath = $WPFMISMountTextBox.Text + '\users\default\appdata\local\microsoft\windows\shell' Update-Log -Data 'Copying the start menu file...' -Class Information Copy-Item $WPFCustomTBStartMenu.Text -Destination $startpath -ErrorAction Stop $filename = (Split-Path -Path $WPFCustomTBStartMenu.Text -Leaf) $OS = $Windowstype if ($os -eq 'Windows 11') { if ($filename -ne 'LayoutModification.json') { $newpath = $startpath + '\' + $filename Update-Log -Data 'Renaming json file...' -Class Warning Rename-Item -Path $newpath -NewName 'LayoutModification.json' Update-Log -Data 'file renamed to LayoutModification.json' -Class Information } } if ($os -ne 'Windows 11') { if ($filename -ne 'LayoutModification.xml') { $newpath = $startpath + '\' + $filename Update-Log -Data 'Renaming xml file...' -Class Warning Rename-Item -Path $newpath -NewName 'LayoutModification.xml' Update-Log -Data 'file renamed to LayoutModification.xml' -Class Information } } } catch { Update-Log -Data "Couldn't apply the start menu XML" -Class Error Update-Log -data $_.Exception.Message -Class Error } } #Function to apply the default application association Function Install-DefaultApplicationAssociations { try { Update-Log -Data 'Applying Default Application Association XML...' "Dism.exe /image:$WPFMISMountTextBox.text /Import-DefaultAppAssociations:$WPFCustomTBDefaultApp.text" Update-log -data 'Default Application Association applied' -Class Information } catch { Update-Log -Data 'Could not apply Default Appklication Association XML...' -Class Error Update-Log -data $_.Exception.Message -Class Error } } #Function to select default app association xml Function Select-DefaultApplicationAssociations { $Sourcexml = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Filter = 'XML (*.xml)|' } $null = $Sourcexml.ShowDialog() $WPFCustomTBDefaultApp.text = $Sourcexml.FileName if ($Sourcexml.FileName -notlike '*.xml') { Update-Log -Data 'A XML file not selected. Please select a valid file to continue.' -Class Warning return } $text = $WPFCustomTBDefaultApp.text + ' selected as the default application XML' Update-Log -Data $text -class Information } #Function to select start menu xml Function Select-StartMenu { $OS = Get-WindowsType if ($OS -ne 'Windows 11') { $Sourcexml = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Filter = 'XML (*.xml)|' } } if ($OS -eq 'Windows 11') { $Sourcexml = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Filter = 'JSON (*.JSON)|' } } $null = $Sourcexml.ShowDialog() $WPFCustomTBStartMenu.text = $Sourcexml.FileName if ($OS -ne 'Windows 11') { if ($Sourcexml.FileName -notlike '*.xml') { Update-Log -Data 'A XML file not selected. Please select a valid file to continue.' -Class Warning return } } if ($OS -eq 'Windows 11') { if ($Sourcexml.FileName -notlike '*.json') { Update-Log -Data 'A JSON file not selected. Please select a valid file to continue.' -Class Warning return } } $text = $WPFCustomTBStartMenu.text + ' selected as the start menu file' Update-Log -Data $text -class Information } #Function to select registry files Function Select-RegFiles { $Regfiles = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') Multiselect = $true # Multiple files can be chosen Filter = 'REG (*.reg)|' } $null = $Regfiles.ShowDialog() $filepaths = $regfiles.FileNames Update-Log -data 'Importing REG files...' -class information foreach ($filepath in $filepaths) { if ($filepath -notlike '*.reg') { Update-Log -Data $filepath -Class Warning Update-Log -Data 'Ignoring this file as it is not a .REG file....' -Class Warning return } Update-Log -Data $filepath -Class Information $WPFCustomLBRegistry.Items.Add($filepath) } Update-Log -data 'REG file importation complete' -class information #Fix this shit, then you can release her. } #Function to apply registry files to mounted image Function Install-RegistryFiles { #mount offline hives Update-Log -Data 'Mounting the offline registry hives...' -Class Information try { $Path = $WPFMISMountTextBox.text + '\Users\Default\NTUser.dat' Update-Log -Data $path -Class Information Invoke-Command { reg load HKLM\OfflineDefaultUser $Path } -ErrorAction Stop | Out-Null $Path = $WPFMISMountTextBox.text + '\Windows\System32\Config\DEFAULT' Update-Log -Data $path -Class Information Invoke-Command { reg load HKLM\OfflineDefault $Path } -ErrorAction Stop | Out-Null $Path = $WPFMISMountTextBox.text + '\Windows\System32\Config\SOFTWARE' Update-Log -Data $path -Class Information Invoke-Command { reg load HKLM\OfflineSoftware $Path } -ErrorAction Stop | Out-Null $Path = $WPFMISMountTextBox.text + '\Windows\System32\Config\SYSTEM' Update-Log -Data $path -Class Information Invoke-Command { reg load HKLM\OfflineSystem $Path } -ErrorAction Stop | Out-Null } catch { Update-Log -Data "Failed to mount $Path" -Class Error Update-Log -data $_.Exception.Message -Class Error } #get reg files from list box $RegFiles = $WPFCustomLBRegistry.items #For Each to process Reg Files and Apply Update-Log -Data 'Processing Reg Files...' -Class Information foreach ($RegFile in $Regfiles) { Update-Log -Data $RegFile -Class Information #write-host $RegFile Try { $Destination = $global:workdir + '\staging\' Update-Log -Data 'Copying file to staging folder...' -Class Information Copy-Item -Path $regfile -Destination $Destination -Force -ErrorAction Stop #Copy Source Registry File to staging } Catch { Update-Log -Data "Couldn't copy reg file" -Class Error Update-Log -data $_.Exception.Message -Class Error } $regtemp = Split-Path $regfile -Leaf #get file name $regpath = $global:workdir + '\staging' + '\' + $regtemp # Write-Host $regpath Try { Update-Log -Data 'Parsing reg file...' ((Get-Content -Path $regpath -Raw) -replace 'HKEY_CURRENT_USER', 'HKEY_LOCAL_MACHINE\OfflineDefaultUser') | Set-Content -Path $regpath -ErrorAction Stop ((Get-Content -Path $regpath -Raw) -replace 'HKEY_LOCAL_MACHINE\\SOFTWARE', 'HKEY_LOCAL_MACHINE\OfflineSoftware') | Set-Content -Path $regpath -ErrorAction Stop ((Get-Content -Path $regpath -Raw) -replace 'HKEY_LOCAL_MACHINE\\SYSTEM', 'HKEY_LOCAL_MACHINE\OfflineSystem') | Set-Content -Path $regpath -ErrorAction Stop ((Get-Content -Path $regpath -Raw) -replace 'HKEY_USERS\\.DEFAULT', 'HKEY_LOCAL_MACHINE\OfflineDefault') | Set-Content -Path $regpath -ErrorAction Stop } Catch { Update-log -Data "Couldn't read or update reg file $regpath" -Class Error Update-Log -data $_.Exception.Message -Class Error } Update-Log -Data 'Reg file has been parsed' -Class Information #import the registry file Try { Update-Log -Data 'Importing registry file into mounted wim' -Class Information Start-Process reg -ArgumentList ('import', "`"$RegPath`"") -Wait -WindowStyle Hidden -ErrorAction stop Update-Log -Data 'Import successful' -Class Information } Catch { Update-Log -Data "Couldn't import $Regpath" -Class Error Update-Log -data $_.Exception.Message -Class Error } } #dismount offline hives try { Update-Log -Data 'Dismounting registry...' -Class Information Invoke-Command { reg unload HKLM\OfflineDefaultUser } -ErrorAction Stop | Out-Null Invoke-Command { reg unload HKLM\OfflineDefault } -ErrorAction Stop | Out-Null Invoke-Command { reg unload HKLM\OfflineSoftware } -ErrorAction Stop | Out-Null Invoke-Command { reg unload HKLM\OfflineSystem } -ErrorAction Stop | Out-Null Update-Log -Data 'Dismount complete' -Class Information } catch { Update-Log -Data "Couldn't dismount the registry hives" -Class Error Update-Log -Data 'This will prevent the Windows image from properly dismounting' -Class Error Update-Log -data $_.Exception.Message -Class Error } } #Function to augment close out window text Function Invoke-DadJoke { $header = @{accept = 'Application/json' } $joke = Invoke-RestMethod -Uri 'https://icanhazdadjoke.com' -Method Get -Headers $header return $joke.joke } #Function to stage and build installer media Function Copy-StageIsoMedia { # if($WPFSourceWIMImgDesTextBox.Text -like '*Windows 10*'){$OS = 'Windows 10'} # if($WPFSourceWIMImgDesTextBox.Text -like '*Server*'){$OS = 'Windows Server'} $OS = Get-WindowsType #$Ver = (Get-WinVersionNumber) $Ver = $MISWinVer #create staging folder try { Update-Log -Data 'Creating staging folder for media' -Class Information New-Item -Path $global:workdir\staging -Name 'Media' -ItemType Directory -ErrorAction Stop | Out-Null Update-Log -Data 'Media staging folder has been created' -Class Information } catch { Update-Log -Data 'Could not create staging folder' -Class Error Update-Log -data $_.Exception.Message -class Error } #copy source to staging try { Update-Log -data 'Staging media binaries...' -Class Information Copy-Item -Path $global:workdir\imports\iso\$OS\$Ver\* -Destination $global:workdir\staging\media -Force -Recurse -ErrorAction Stop Update-Log -data 'Media files have been staged' -Class Information } catch { Update-Log -Data 'Failed to stage media binaries...' -Class Error Update-Log -data $_.Exception.Message -class Error } } #Function to create the ISO file from staged installer media Function New-WindowsISO { if ((Test-Path -Path ${env:ProgramFiles(x86)}'\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe' -PathType Leaf) -eq $false) { Update-Log -Data 'The file oscdimg.exe was not found. Skipping ISO creation...' -Class Error return } If ($WPFMISTBISOFileName.Text -notlike '*.iso') { $WPFMISTBISOFileName.Text = $WPFMISTBISOFileName.Text + '.iso' Update-Log -Data 'Appending new file name with an extension' -Class Information } $Location = ${env:ProgramFiles(x86)} $executable = $location + '\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe' $bootbin = $global:workdir + '\staging\media\efi\microsoft\boot\efisys.bin' $source = $global:workdir + '\staging\media' $folder = $WPFMISTBFilePath.text $file = $WPFMISTBISOFileName.text $dest = "$folder\$file" $text = "-b$bootbin" if ((Test-Path -Path $dest) -eq $true) { Rename-Name -file $dest -extension '.iso' } try { Update-Log -Data 'Starting to build ISO...' -Class Information # write-host $executable Start-Process $executable -args @("`"$text`"", '-pEF', '-u1', '-udfver102', "`"$source`"", "`"$dest`"") -Wait -ErrorAction Stop Update-Log -Data 'ISO has been built' -Class Information } catch { Update-Log -Data "Couldn't create the ISO file" -Class Error Update-Log -data $_.Exception.Message -class Error } } #Function to copy staged installer media to CM Package Share Function Copy-UpgradePackage { #copy staging folder to destination with force parameter try { Update-Log -data 'Copying updated media to Upgrade Package folder...' -Class Information Copy-Item -Path $global:workdir\staging\media\* -Destination $WPFMISTBUpgradePackage.text -Force -Recurse -ErrorAction Stop Update-Log -Data 'Updated media has been copied' -Class Information } catch { Update-Log -Data "Couldn't copy the updated media to the upgrade package folder" -Class Error Update-Log -data $_.Exception.Message -class Error } } #Function to update the boot wim in the staged installer media folder Function Update-BootWIM { #create mount point in staging try { Update-Log -Data 'Creating mount point in staging folder...' New-Item -Path $global:workdir\staging -Name 'mount' -ItemType Directory -ErrorAction Stop Update-Log -Data 'Staging folder mount point created successfully' -Class Information } catch { Update-Log -data 'Failed to create the staging folder mount point' -Class Error Update-Log -data $_.Exception.Message -class Error return } #change attribute of boot.wim #Change file attribute to normal Update-Log -Data 'Setting file attribute of boot.wim to Normal' -Class Information $attrib = Get-Item $global:workdir\staging\media\sources\boot.wim $attrib.Attributes = 'Normal' $BootImages = Get-WindowsImage -ImagePath $global:workdir\staging\media\sources\boot.wim Foreach ($BootImage in $BootImages) { #Mount the PE Image try { $text = 'Mounting PE image number ' + $BootImage.ImageIndex Update-Log -data $text -Class Information Mount-WindowsImage -ImagePath $global:workdir\staging\media\sources\boot.wim -Path $global:workdir\staging\mount -Index $BootImage.ImageIndex -ErrorAction Stop } catch { Update-Log -Data 'Could not mount the boot.wim' -Class Error Update-Log -data $_.Exception.Message -class Error return } Update-Log -data 'Applying SSU Update' -Class Information Deploy-Updates -class 'PESSU' Update-Log -data 'Applying LCU Update' -Class Information Deploy-Updates -class 'PELCU' #Dismount the PE Image try { Update-Log -data 'Dismounting Windows PE image...' -Class Information Dismount-WindowsImage -Path $global:workdir\staging\mount -Save -ErrorAction Stop } catch { Update-Log -data 'Could not dismount the winpe image.' -Class Error Update-Log -data $_.Exception.Message -class Error } #Export the WinPE Image Try { Update-Log -data 'Exporting WinPE image index...' -Class Information Export-WindowsImage -SourceImagePath $global:workdir\staging\media\sources\boot.wim -SourceIndex $BootImage.ImageIndex -DestinationImagePath $global:workdir\staging\tempboot.wim -ErrorAction Stop } catch { Update-Log -Data 'Failed to export WinPE image' -Class Error Update-Log -data $_.Exception.Message -class Error } } #Overwrite the stock boot.wim file with the updated one try { Update-Log -Data 'Overwriting boot.wim with updated and optimized version...' -Class Information Move-Item -Path $global:workdir\staging\tempboot.wim -Destination $global:workdir\staging\media\sources\boot.wim -Force -ErrorAction Stop Update-Log -Data 'Boot.WIM updated successfully' -Class Information } catch { Update-Log -Data 'Could not copy the updated boot.wim' -Class Error Update-Log -data $_.Exception.Message -class Error } } #Function to update windows recovery in the mounted offline image Function Update-WinReWim { #create mount point in staging #copy winre from mounted offline image #change attribute of winre.wim #mount staged winre.wim #update, dismount #copy wim back to mounted offline image } #Function to retrieve windows version Function Get-WinVersionNumber { $buildnum = $null # Latest 10 Windows 10 version checks switch -Regex ($WPFSourceWimVerTextBox.text) { '10\.0\.19045\.3930' { $buildnum = '22H2' } '10\.0\.19044\.3930' { $buildnum = '21H2' } '10\.0\.19045\.3803' { $buildnum = '22H2' } '10\.0\.19044\.3803' { $buildnum = '21H2' } '10\.0\.19045\.3693' { $buildnum = '22H2' } '10\.0\.19044\.3693' { $buildnum = '21H2' } '10\.0\.19045\.3570' { $buildnum = '22H2' } '10\.0\.19044\.3570' { $buildnum = '21H2' } '10\.0\.19045\.3448' { $buildnum = '22H2' } '10\.0\.19044\.3448' { $buildnum = '21H2' } # Windows 11 version checks '10\.0\.22631\.3007' { $buildnum = '23H2' } '10\.0\.22621\.3007' { $buildnum = '22H2' } '10\.0\.22631\.2715' { $buildnum = '23H2' } '10\.0\.22621\.2861' { $buildnum = '23H2' } '10\.0\.22000\.2713' { $buildnum = '21H2' } '10\.0\.22621\.2428' { $buildnum = '22H2' } # Add all other specific Windows 11 build checks here... '10\.0\.22621\.2134' { $buildnum = '22H2' } '10\.0\.22000\.2652' { $buildnum = '21H2' } # Continue for all provided Windows 11 builds Default { $buildnum = 'Unknown Version' } } If ($WPFSourceWimVerTextBox.text -like '10.0.19041.*') { $IsMountPoint = $False $currentmounts = Get-WindowsImage -Mounted foreach ($currentmount in $currentmounts) { if ($currentmount.path -eq $WPFMISMountTextBox.text) { $IsMountPoint = $true } } #IS a mount path If ($IsMountPoint -eq $true) { $mountdir = $WPFMISMountTextBox.Text reg LOAD HKLM\OFFLINE $mountdir\Windows\System32\Config\SOFTWARE | Out-Null $regvalues = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\OFFLINE\Microsoft\Windows NT\CurrentVersion\' ) $buildnum = $regvalues.ReleaseId if ($regvalues.ReleaseId -eq '2009') { if ($regvalues.CurrentBuild -eq '19042') { $buildnum = '2009' } if ($regvalues.CurrentBuild -eq '19043') { $buildnum = '21H1' } if ($regvalues.CurrentBuild -eq '19044') { $buildnum = '21H2' } if ($regvalues.CurrentBuild -eq '19045') { $buildnum = '22H2' } } reg UNLOAD HKLM\OFFLINE | Out-Null } If ($IsMountPoint -eq $False) { $global:Win10VerDet = $null Update-Log -data 'Prompting user for Win10 version confirmation...' -class Information Invoke-19041Select if ($null -eq $global:Win10VerDet) { return } $temp = $global:Win10VerDet $buildnum = $temp Update-Log -data "User selected $buildnum" -class Information $global:Win10VerDet = $null } } return $buildnum } #funcation to select ISO creation path Function Select-ISODirectory { Add-Type -AssemblyName System.Windows.Forms $browser = New-Object System.Windows.Forms.FolderBrowserDialog $browser.Description = 'Select the folder to save the ISO' $null = $browser.ShowDialog() $MountDir = $browser.SelectedPath $WPFMISTBFilePath.text = $MountDir #Test-MountPath -path $WPFMISMountTextBox.text Update-Log -Data 'ISO directory selected' -Class Information } #Function to determine if WIM is Win10 or Windows Server Function Get-WindowsType { if ($WPFSourceWIMImgDesTextBox.text -like '*Windows 10*') { $type = 'Windows 10' } if ($WPFSourceWIMImgDesTextBox.text -like '*Windows Server*') { $type = 'Windows Server' } if ($WPFSourceWIMImgDesTextBox.text -like '*Windows 11*') { $type = 'Windows 11' } Return $type } #Function to check if ISO binaries exist Function Test-IsoBinariesExist { $buildnum = Get-WinVersionNumber $OSType = get-Windowstype $ISOFiles = $global:workdir + '\imports\iso\' + $OSType + '\' + $buildnum + '\' Test-Path -Path $ISOFiles\* if ((Test-Path -Path $ISOFiles\*) -eq $false) { $text = 'ISO Binaries are not present for ' + $OSType + ' ' + $buildnum Update-Log -Data $text -Class Warning Update-Log -data 'Import ISO Binaries from an ISO or disable ISO/Upgrade Package creation' -Class Warning return $false } } #Function to clear partial checkboxes when importing config file Function Invoke-CheckboxCleanup { Update-Log -Data 'Cleaning null checkboxes...' -Class Information $Variables = Get-Variable WPF* foreach ($variable in $variables) { if ($variable.value -like '*.CheckBox*') { #write-host $variable.name #write-host $variable.value.IsChecked if ($variable.value.IsChecked -ne $true) { $variable.value.IsChecked = $false } } } } #Function to really make sure the ISO mount is gone! Function Invoke-RemoveISOMount ($inputObject) { DO { Dismount-DiskImage -InputObject $inputObject } while (Dismount-DiskImage -InputObject $inputObject) #He's dead Jim! Update-Log -data 'Dismount complete' -class Information } #Function to install CM Console extensions Function Install-WWCMConsoleExtension { $UpdateWWXML = @" <ActionDescription Class ="Executable" DisplayName="Update with WIM Witch" MnemonicDisplayName="Update with WIM Witch" Description="Click to update the image with WIM Witch"> <ShowOn> <string>ContextMenu</string> <string>DefaultHomeTab</string> </ShowOn> <Executable> <FilePath>$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe</FilePath> <Parameters> -ExecutionPolicy Bypass -File "$PSCommandPath" -auto -autofile "$global:workdir\ConfigMgr\PackageInfo\##SUB:PackageID##"</Parameters> </Executable> </ActionDescription> "@ $EditWWXML = @" <ActionDescription Class ="Executable" DisplayName="Edit WIM Witch Image Config" MnemonicDisplayName="Edit WIM Witch Image Config" Description="Click to edit the WIM Witch image configuration"> <ShowOn> <string>ContextMenu</string> <string>DefaultHomeTab</string> </ShowOn> <Executable> <FilePath>$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe</FilePath> <Parameters> -ExecutionPolicy Bypass -File "$PSCommandPath" -CM "Edit" -autofile "$global:workdir\ConfigMgr\PackageInfo\##SUB:PackageID##"</Parameters> </Executable> </ActionDescription> "@ $NewWWXML = @" <ActionDescription Class ="Executable" DisplayName="New WIM Witch Image" MnemonicDisplayName="New WIM Witch Image" Description="Click to create a new WIM Witch image"> <ShowOn> <string>ContextMenu</string> <string>DefaultHomeTab</string> </ShowOn> <Executable> <FilePath>$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe</FilePath> <Parameters> -ExecutionPolicy Bypass -File "$PSCommandPath" -CM "New"</Parameters> </Executable> </ActionDescription> "@ Update-Log -Data 'Installing ConfigMgr console extension...' -Class Information $ConsoleFolderImage = '828a154e-4c7d-4d7f-ba6c-268443cdb4e8' #folder for update and edit $ConsoleFolderRoot = 'ac16f420-2d72-4056-a8f6-aef90e66a10c' #folder for new $path = ($env:SMS_ADMIN_UI_PATH -replace 'bin\\i386', '') + 'XmlStorage\Extensions\Actions' Update-Log -Data 'Creating folders if needed...' -Class Information if ((Test-Path -Path (Join-Path -Path $path -ChildPath $ConsoleFolderImage)) -eq $false) { New-Item -Path $path -Name $ConsoleFolderImage -ItemType 'directory' | Out-Null } Update-Log -data 'Creating extension files...' -Class Information $UpdateWWXML | Out-File ((Join-Path -Path $path -ChildPath $ConsoleFolderImage) + '\UpdateWWImage.xml') -Force $EditWWXML | Out-File ((Join-Path -Path $path -ChildPath $ConsoleFolderImage) + '\EditWWImage.xml') -Force Update-Log -Data 'Creating folders if needed...' -Class Information if ((Test-Path -Path (Join-Path -Path $path -ChildPath $ConsoleFolderRoot)) -eq $false) { New-Item -Path $path -Name $ConsoleFolderRoot -ItemType 'directory' | Out-Null } Update-Log -data 'Creating extension files...' -Class Information $NewWWXML | Out-File ((Join-Path -Path $path -ChildPath $ConsoleFolderRoot) + '\NewWWImage.xml') -Force Update-Log -Data 'Console extension installation complete!' -Class Information } #Function to handle 32-Bit PowerSehell Function Invoke-ArchitectureCheck { if ([Environment]::Is64BitProcess -ne [Environment]::Is64BitOperatingSystem) { Update-Log -Data 'This is 32-bit PowerShell session. Will relaunch as 64-bit...' -Class Warning #The following If statment was pilfered from Michael Niehaus if (Test-Path "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") { if (($auto -eq $false) -and ($CM -eq 'None')) { & "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath" } if (($auto -eq $true) -and ($null -ne $autofile)) { & "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath" -auto -autofile $autofile } if (($CM -eq 'Edit') -and ($null -ne $autofile)) { & "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath" -CM Edit -autofile $autofile } if ($CM -eq 'New') { & "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath" -CM New } Exit $lastexitcode } } else { Update-Log -Data 'This is a 64 bit PowerShell session' -Class Information } } #Function to download and extract the SSU required for 2004/20H2 June '21 LCU Function Invoke-2XXXPreReq { $KB_URI = 'http://download.windowsupdate.com/c/msdownload/update/software/secu/2021/05/windows10.0-kb5003173-x64_375062f9d88a5d9d11c5b99673792fdce8079e09.cab' $executable = "$env:windir\system32\expand.exe" $mountdir = $WPFMISMountTextBox.Text Update-Log -data 'Mounting offline registry and validating UBR / Patch level...' -class Information reg LOAD HKLM\OFFLINE $mountdir\Windows\System32\Config\SOFTWARE | Out-Null $regvalues = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\OFFLINE\Microsoft\Windows NT\CurrentVersion\' ) Update-Log -data 'The UBR (Patch Level) is:' -class Information Update-Log -data $regvalues.ubr -class information reg UNLOAD HKLM\OFFLINE | Out-Null if ($null -eq $regvalues.ubr) { Update-Log -data "Registry key wasn't copied. Can't continue." -class Error return 1 } if ($regvalues.UBR -lt '985') { Update-Log -data 'The image requires an additional required SSU.' -class Information Update-Log -data 'Checking to see if the required SSU exists...' -class Information if ((Test-Path "$global:workdir\updates\Windows 10\2XXX_prereq\SSU-19041.985-x64.cab") -eq $false) { Update-Log -data 'The required SSU does not exist. Downloading it now...' -class Information try { Invoke-WebRequest -Uri $KB_URI -OutFile "$global:workdir\staging\extract_me.cab" -ErrorAction stop } catch { Update-Log -data 'Failed to download the update' -class Error Update-Log -data $_.Exception.Message -Class Error return 1 } if ((Test-Path "$global:workdir\updates\Windows 10\2XXX_prereq") -eq $false) { try { Update-Log -data 'The folder for the required SSU does not exist. Creating it now...' -class Information New-Item -Path "$global:workdir\updates\Windows 10" -Name '2XXX_prereq' -ItemType Directory -ErrorAction stop | Out-Null Update-Log -data 'The folder has been created' -class information } catch { Update-Log -data 'Could not create the required folder.' -class error Update-Log -data $_.Exception.Message -Class Error return 1 } } try { Update-Log -data 'Extracting the SSU from the May 2021 LCU...' -class Information Start-Process $executable -args @("`"$global:workdir\staging\extract_me.cab`"", '/f:*SSU*.CAB', "`"$global:workdir\updates\Windows 10\2XXX_prereq`"") -Wait -ErrorAction Stop Update-Log 'Extraction of SSU was success' -class information } catch { Update-Log -data "Couldn't extract the SSU from the LCU" -class error Update-Log -data $_.Exception.Message -Class Error return 1 } try { Update-Log -data 'Deleting the staged LCU file...' -class Information Remove-Item -Path $global:workdir\staging\extract_me.cab -Force -ErrorAction stop | Out-Null Update-Log -data 'The source file for the SSU has been Baleeted!' -Class Information } catch { Update-Log -data 'Could not delete the source package' -Class Error Update-Log -data $_.Exception.Message -Class Error return 1 } } else { Update-Log -data 'The required SSU exists. No need to download' -Class Information } try { Update-Log -data 'Applying the SSU...' -class Information Add-WindowsPackage -PackagePath "$global:workdir\updates\Windows 10\2XXX_prereq" -Path $WPFMISMountTextBox.Text -ErrorAction Stop | Out-Null Update-Log -data 'SSU applied successfully' -class Information } catch { Update-Log -data "Couldn't apply the SSU update" -class error Update-Log -data $_.Exception.Message -Class Error return 1 } } else { Update-Log -Data "Image doesn't require the prereq SSU" -Class Information } Update-Log -data 'SSU remdiation complete' -Class Information return 0 } #Function to display text notification to end user Function Invoke-TextNotification { Update-Log -data '*********************************' -class Comment Update-Log -data '*********************************' -class Comment } #Function to display Windows 10 v2XXX selection pop up Function Invoke-19041Select { $inputXML = @' <Window x:Class="popup.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:popup" mc:Ignorable="d" Title="Select Win10 Version" Height="170" Width="353"> <Grid x:Name="Win10PU" Margin="0,0,10,6"> <ComboBox x:Name="Win10PUCombo" HorizontalAlignment="Left" Margin="40,76,0,0" VerticalAlignment="Top" Width="120"/> <Button x:Name="Win10PUOK" Content="OK" HorizontalAlignment="Left" Margin="182,76,0,0" VerticalAlignment="Top" Width="50"/> <Button x:Name="Win10PUCancel" Content="Cancel" HorizontalAlignment="Left" Margin="248,76,0,0" VerticalAlignment="Top" Width="50"/> <TextBlock x:Name="Win10PUText" HorizontalAlignment="Left" Margin="24,27,0,0" Text="Please selet the correct version of Windows 10." TextWrapping="Wrap" VerticalAlignment="Top" Grid.ColumnSpan="2"/> </Grid> </Window> '@ $inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace 'x:N', 'N' -replace '^<Win.*', '<Window' [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') [xml]$XAML = $inputXML #Read XAML $reader = (New-Object System.Xml.XmlNodeReader $xaml) try { $Form = [Windows.Markup.XamlReader]::Load( $reader ) } catch { Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged or TextChanged properties in your textboxes (PowerShell cannot process them)" throw } $xaml.SelectNodes('//*[@Name]') | ForEach-Object { "trying item $($_.Name)" | Out-Null try { Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop } catch { throw } } Function Get-FormVariables { if ($global:ReadmeDisplay -ne $true) { #Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true } #write-host "Found the following interactable elements from our form" -ForegroundColor Cyan Get-Variable WPF* } Get-FormVariables | Out-Null #Combo Box population $Win10VerNums = @('20H2', '21H1', '21H2', '22H2') Foreach ($Win10VerNum in $Win10VerNums) { $WPFWin10PUCombo.Items.Add($Win10VerNum) | Out-Null } #Button_OK_Click $WPFWin10PUOK.Add_Click({ $global:Win10VerDet = $WPFWin10PUCombo.SelectedItem $Form.Close() return }) #Button_Cancel_Click $WPFWin10PUCancel.Add_Click({ $global:Win10VerDet = $null Update-Log -data 'User cancelled the confirmation dialog box' -Class Warning $Form.Close() return }) $Form.ShowDialog() | Out-Null } #Function for the Make it So button Function Invoke-MakeItSo ($appx) { #Check if new file name is valid, also append file extension if neccessary ###Starting MIS Preflight### Test-MountPath -path $WPFMISMountTextBox.Text -clean True if (($WPFMISWimNameTextBox.Text -eq '') -or ($WPFMISWimNameTextBox.Text -eq 'Enter Target WIM Name')) { Update-Log -Data 'Enter a valid file name and then try again' -Class Error return } if (($auto -eq $false) -and ($WPFCMCBImageType.SelectedItem -ne 'Update Existing Image' )) { $checkresult = (Test-Name) if ($checkresult -eq 'stop') { return } } #check for working directory, make if does not exist, delete files if they exist Update-Log -Data 'Checking to see if the staging path exists...' -Class Information try { if (!(Test-Path "$global:workdir\Staging" -PathType 'Any')) { New-Item -ItemType Directory -Force -Path $global:workdir\Staging -ErrorAction Stop Update-Log -Data 'Path did not exist, but it does now' -Class Information -ErrorAction Stop } else { Remove-Item -Path $global:workdir\Staging\* -Recurse -ErrorAction Stop Update-Log -Data 'The path existed, and it has been purged.' -Class Information -ErrorAction Stop } } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "Something is wrong with folder $global:workdir\Staging. Try deleting manually if it exists" -Class Error return } if ($WPFJSONEnableCheckBox.IsChecked -eq $true) { Update-Log -Data 'Validating existance of JSON file...' -Class Information $APJSONExists = (Test-Path $WPFJSONTextBox.Text) if ($APJSONExists -eq $true) { Update-Log -Data 'JSON exists. Continuing...' -Class Information } else { Update-Log -Data 'The Autopilot file could not be verified. Check it and try again.' -Class Error return } } if ($WPFMISDotNetCheckBox.IsChecked -eq $true) { if ((Test-DotNetExists) -eq $False) { return } } #Check for free space if ($SkipFreeSpaceCheck -eq $false) { if (Test-FreeSpace -eq 1) { Update-Log -Data 'Insufficient free space. Delete some files and try again' -Class Error return } else { Update-Log -Data 'There is sufficient free space.' -Class Information } } #####End of MIS Preflight################################################################### #Copy source WIM Update-Log -Data 'Copying source WIM to the staging folder' -Class Information try { Copy-Item $WPFSourceWIMSelectWIMTextBox.Text -Destination "$global:workdir\Staging" -ErrorAction Stop } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -Data "The file couldn't be copied. No idea what happened" -class Error return } Update-Log -Data 'Source WIM has been copied to the source folder' -Class Information #Rename copied source WiM try { $wimname = Get-Item -Path $global:workdir\Staging\*.wim -ErrorAction Stop Rename-Item -Path $wimname -NewName $WPFMISWimNameTextBox.Text -ErrorAction Stop Update-Log -Data 'Copied source WIM has been renamed' -Class Information } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "The copied source file couldn't be renamed. This shouldn't have happened." -Class Error Update-Log -data "Go delete the WIM from $global:workdir\Staging\, then try again" -Class Error return } #Remove the unwanted indexes Remove-OSIndex #Mount the WIM File $wimname = Get-Item -Path $global:workdir\Staging\*.wim Update-Log -Data "Mounting source WIM $wimname" -Class Information Update-Log -Data 'to mount point:' -Class Information Update-Log -data $WPFMISMountTextBox.Text -Class Information try { Mount-WindowsImage -Path $WPFMISMountTextBox.Text -ImagePath $wimname -Index 1 -ErrorAction Stop | Out-Null } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "The WIM couldn't be mounted. Make sure the mount directory is empty" -Class Error Update-Log -Data "and that it isn't an active mount point" -Class Error return } #checks to see if the iso binaries exist. Cancel and discard WIM if they are not present. If (($WPFMISCBISO.IsChecked -eq $true) -or ($WPFMISCBUpgradePackage.IsChecked -eq $true)) { if ((Test-IsoBinariesExist) -eq $False) { Update-Log -Data 'Discarding WIM and not making it so' -Class Error Dismount-WindowsImage -Path $WPFMISMountTextBox.Text -Discard -ErrorAction Stop | Out-Null return } } #Get Mounted WIM version and save it to a variable for useage later in the Function $MISWinVer = (Get-WinVersionNumber) #Pause after mounting If ($WPFMISCBPauseMount.IsChecked -eq $True) { Update-Log -Data 'Pausing image building. Waiting on user to continue...' -Class Warning $Pause = Suspend-MakeItSo if ($Pause -eq 'Yes') { Update-Log -data 'Continuing on with making it so...' -Class Information } if ($Pause -eq 'No') { Update-Log -data 'Discarding build...' -Class Error Update-Log -Data 'Discarding mounted WIM' -Class Warning Dismount-WindowsImage -Path $WPFMISMountTextBox.Text -Discard -ErrorAction Stop | Out-Null Update-Log -Data 'WIM has been discarded. Better luck next time.' -Class Warning return } } #Run Script after mounting if (($WPFCustomCBRunScript.IsChecked -eq $True) -and ($WPFCustomCBScriptTiming.SelectedItem -eq 'After image mount')) { Update-Log -data 'Running PowerShell script...' -Class Information Start-Script -file $WPFCustomTBFile.text -parameter $WPFCustomTBParameters.text Update-Log -data 'Script completed.' -Class Information } #Language Packs and FOD if ($WPFCustomCBLangPacks.IsChecked -eq $true) { Install-LanguagePacks } else { Update-Log -Data 'Language Packs Injection not selected. Skipping...' } if ($WPFCustomCBLEP.IsChecked -eq $true) { Install-LocalExperiencePack } else { Update-Log -Data 'Local Experience Packs not selected. Skipping...' } if ($WPFCustomCBFOD.IsChecked -eq $true) { Install-FeaturesOnDemand } else { Update-Log -Data 'Features On Demand not selected. Skipping...' } #Inject .Net Binaries if ($WPFMISDotNetCheckBox.IsChecked -eq $true) { Add-DotNet } #Inject Autopilot JSON file if ($WPFJSONEnableCheckBox.IsChecked -eq $true) { Update-Log -Data 'Injecting JSON file' -Class Information try { $autopilotdir = $WPFMISMountTextBox.Text + '\windows\Provisioning\Autopilot' Copy-Item $WPFJSONTextBox.Text -Destination $autopilotdir -ErrorAction Stop } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "JSON file couldn't be copied. Check to see if the correct SKU" -Class Error Update-Log -Data 'of Windows has been selected' -Class Error Update-log -Data "The WIM is still mounted. You'll need to clean that up manually until" -Class Error Update-Log -data 'I get around to handling that error more betterer' -Class Error return } } else { Update-Log -Data 'JSON not selected. Skipping JSON Injection' -Class Information } #Inject Drivers If ($WPFDriverCheckBox.IsChecked -eq $true) { Start-DriverInjection -Folder $WPFDriverDir1TextBox.text Start-DriverInjection -Folder $WPFDriverDir2TextBox.text Start-DriverInjection -Folder $WPFDriverDir3TextBox.text Start-DriverInjection -Folder $WPFDriverDir4TextBox.text Start-DriverInjection -Folder $WPFDriverDir5TextBox.text } Else { Update-Log -Data 'Drivers were not selected for injection. Skipping.' -Class Information } #Inject default application association XML if ($WPFCustomCBEnableApp.IsChecked -eq $true) { Install-DefaultApplicationAssociations } else { Update-Log -Data 'Default Application Association not selected. Skipping...' -Class Information } #Inject start menu layout if ($WPFCustomCBEnableStart.IsChecked -eq $true) { Install-StartLayout } else { Update-Log -Data 'Start Menu Layout injection not selected. Skipping...' -Class Information } #apply registry files if ($WPFCustomCBEnableRegistry.IsChecked -eq $true) { Install-RegistryFiles } else { Update-Log -Data 'Registry file injection not selected. Skipping...' -Class Information } #Check for updates when ConfigMgr source is selected if ($WPFMISCBCheckForUpdates.IsChecked -eq $true) { Invoke-MISUpdates if (($WPFSourceWIMImgDesTextBox.text -like '*Windows 10*') -or ($WPFSourceWIMImgDesTextBox.text -like '*Windows 11*')) { Get-OneDrive } } #Apply Updates If ($WPFUpdatesEnableCheckBox.IsChecked -eq $true) { Deploy-Updates -class 'SSU' Deploy-Updates -class 'LCU' Deploy-Updates -class 'AdobeSU' Deploy-Updates -class 'DotNet' Deploy-Updates -class 'DotNetCU' #if ($WPFUpdatesCBEnableDynamic.IsChecked -eq $True){Deploy-Updates -class "Dynamic"} if ($WPFUpdatesOptionalEnableCheckBox.IsChecked -eq $True) { Deploy-Updates -class 'Optional' } } else { Update-Log -Data 'Updates not enabled' -Class Information } #Copy the current OneDrive installer if ($WPFMISOneDriveCheckBox.IsChecked -eq $true) { $os = Get-WindowsType $build = Get-WinVersionNumber if (($os -eq 'Windows 11') -and ($build -eq '22H2') -or ($build -eq '23H2')) { Copy-OneDrivex64 } else { Copy-OneDrive } } else { Update-Log -data 'OneDrive agent update skipped as it was not selected' -Class Information } #Remove AppX Packages if ($WPFAppxCheckBox.IsChecked -eq $true) { Remove-Appx -array $appx } Else { Update-Log -Data 'App removal not enabled' -Class Information } #Run Script before dismount if (($WPFCustomCBRunScript.IsChecked -eq $True) -and ($WPFCustomCBScriptTiming.SelectedItem -eq 'Before image dismount')) { Start-Script -file $WPFCustomTBFile.text -parameter $WPFCustomTBParameters.text } #Pause before dismounting If ($WPFMISCBPauseDismount.IsChecked -eq $True) { Update-Log -Data 'Pausing image building. Waiting on user to continue...' -Class Warning $Pause = Suspend-MakeItSo if ($Pause -eq 'Yes') { Update-Log -data 'Continuing on with making it so...' -Class Information } if ($Pause -eq 'No') { Update-Log -data 'Discarding build...' -Class Error Update-Log -Data 'Discarding mounted WIM' -Class Warning Dismount-WindowsImage -Path $WPFMISMountTextBox.Text -Discard -ErrorAction Stop | Out-Null Update-Log -Data 'WIM has been discarded. Better luck next time.' -Class Warning return } } #Copy log to mounted WIM try { Update-Log -Data 'Attempting to copy log to mounted image' -Class Information $mountlogdir = $WPFMISMountTextBox.Text + '\windows\' Copy-Item $global:workdir\logging\WIMWitch.log -Destination $mountlogdir -ErrorAction Stop $CopyLogExist = Test-Path $mountlogdir\WIMWitch.log -PathType Leaf if ($CopyLogExist -eq $true) { Update-Log -Data 'Log filed copied successfully' -Class Information } } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "Coudn't copy the log file to the mounted image." -class Error } #Dismount, commit, and move WIM Update-Log -Data 'Dismounting WIM file, committing changes' -Class Information try { Dismount-WindowsImage -Path $WPFMISMountTextBox.Text -Save -ErrorAction Stop | Out-Null } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "The WIM couldn't save. You will have to manually discard the" -Class Error Update-Log -data 'mounted image manually' -Class Error return } Update-Log -Data 'WIM dismounted' -Class Information #Display new version number $WimInfo = (Get-WindowsImage -ImagePath $wimname -Index 1) $text = 'New image version number is ' + $WimInfo.Version Update-Log -data $text -Class Information if (($auto -eq $true) -or ($WPFCMCBImageType.SelectedItem -eq 'Update Existing Image')) { Update-Log -Data 'Backing up old WIM file...' -Class Information $checkresult = (Test-Name -conflict append) if ($checkresult -eq 'stop') { return } } #stage media if check boxes are selected if (($WPFMISCBUpgradePackage.IsChecked -eq $true) -or ($WPFMISCBISO.IsChecked -eq $true)) { Copy-StageIsoMedia Update-Log -Data 'Exporting install.wim to media staging folder...' -Class Information Export-WindowsImage -SourceImagePath $wimname -SourceIndex 1 -DestinationImagePath ($global:workdir + '\staging\media\sources\install.wim') -DestinationName ('WW - ' + $WPFSourceWIMImgDesTextBox.text) | Out-Null } #Export the wim file to various locations if ($WPFMISCBNoWIM.IsChecked -ne $true) { try { Update-Log -Data 'Exporting WIM file' -Class Information Export-WindowsImage -SourceImagePath $wimname -SourceIndex 1 -DestinationImagePath ($WPFMISWimFolderTextBox.Text + '\' + $WPFMISWimNameTextBox.Text) -DestinationName ('WW - ' + $WPFSourceWIMImgDesTextBox.text) | Out-Null } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "The WIM couldn't be exported. You can still retrieve it from staging path." -Class Error Update-Log -data 'The file will be deleted when the tool is rerun.' -Class Error return } Update-Log -Data 'WIM successfully exported to target folder' -Class Information } #ConfigMgr Integration if ($WPFCMCBImageType.SelectedItem -ne 'Disabled') { # "New Image","Update Existing Image" if ($WPFCMCBImageType.SelectedItem -eq 'New Image') { Update-Log -data 'Creating a new image in ConfigMgr...' -class Information New-CMImagePackage } if ($WPFCMCBImageType.SelectedItem -eq 'Update Existing Image') { Update-Log -data 'Updating the existing image in ConfigMgr...' -class Information Update-CMImage } } #Apply Dynamic Update to media if ($WPFMISCBDynamicUpdates.IsChecked -eq $true) { Deploy-Updates -class 'Dynamic' } else { Update-Log -data 'Dynamic Updates skipped or not applicable' -Class Information } #Apply updates to the boot.wim file if ($WPFMISCBBootWIM.IsChecked -eq $true) { Update-BootWIM } else { Update-Log -data 'Updating Boot.WIM skipped or not applicable' -Class Information } #Copy upgrade package binaries if selected if ($WPFMISCBUpgradePackage.IsChecked -eq $true) { Copy-UpgradePackage } else { Update-Log -Data 'Upgrade Package skipped or not applicable' -Class Information } #Create ISO if selected if ($WPFMISCBISO.IsChecked -eq $true) { New-WindowsISO } else { Update-Log -Data 'ISO Creation skipped or not applicable' -Class Information } #Run Script when build complete if (($WPFCustomCBRunScript.IsChecked -eq $True) -and ($WPFCustomCBScriptTiming.SelectedItem -eq 'On build completion')) { Start-Script -file $WPFCustomTBFile.text -parameter $WPFCustomTBParameters.text } #Clear out staging folder try { Update-Log -Data 'Clearing staging folder...' -Class Information Remove-Item $global:workdir\staging\* -Force -Recurse -ErrorAction Stop } catch { Update-Log -Data 'Could not clear staging folder' -Class Warning Update-Log -data $_.Exception.Message -class Error } #Copy log here try { Update-Log -Data 'Copying build log to target folder' -Class Information Copy-Item -Path $global:workdir\logging\WIMWitch.log -Destination $WPFMISWimFolderTextBox.Text -ErrorAction Stop $logold = $WPFMISWimFolderTextBox.Text + '\WIMWitch.log' $lognew = $WPFMISWimFolderTextBox.Text + '\' + $WPFMISWimNameTextBox.Text + '.log' #Put log detection code here if ((Test-Path -Path $lognew) -eq $true) { Update-Log -Data 'A preexisting log file contains the same name. Renaming old log...' -Class Warning Rename-Name -file $lognew -extension '.log' } #Put log detection code here Rename-Item $logold -NewName $lognew -Force -ErrorAction Stop Update-Log -Data 'Log copied successfully' -Class Information } catch { Update-Log -data $_.Exception.Message -class Error Update-Log -data "The log file couldn't be copied and renamed. You can still snag it from the source." -Class Error Update-Log -Data "Job's done." -Class Information return } Update-Log -Data "Job's done." -Class Information } #endregion Functions |