ozo-windows-installer-customizer.ps1
#Requires -Modules Dism,OZOLogger -Version 5.1 -RunAsAdministrator <#PSScriptInfo .VERSION 0.1.0 .GUID 798c9379-3b13-41c0-a2d9-04c7957dff49 .AUTHOR Andy Lievertz <alievertz@onezeroone.dev> .COMPANYNAME One Zero One .COPYRIGHT This script is released under the terms of the GNU Public License ("GPL") version 2.0. .TAGS .LICENSEURI https://github.com/onezeroone-dev/OZO-Windows-Installer-Customizer/blob/main/LICENSE .PROJECTURI https://github.com/onezeroone-dev/OZO-Windows-Installer-Customizer .ICONURI .EXTERNALMODULEDEPENDENCIES Dism,OZOLogger .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES https://github.com/onezeroone-dev/OZO-Windows-Installer-Customizer/blob/main/CHANGELOG.md .PRIVATEDATA #> <# .SYNOPSIS See description. .DESCRIPTION Customizes the Windows installer ISO based on a JSON configuration file containing parameters for OS, version, edition, and features. It enables automation with an Answer File, can include custom media (wallpapers, logos, etc.), and can remove undesired AppX packages .PARAMETER Configuration The path to the JSON configuration file. Defaults to ozo-windows-installer-customizer.json in the same directory as this script. .PARAMETER Nocleanup Do not clean up the temporary file assets. Mostly used for testing and debugging. .EXAMPLE ozo-windows-installer-customizer -Configuration "C:\Imaging\Configuration\ozo-windows-installer-customizer.json" .LINK https://github.com/onezeroone-dev/OZO-Windows-Installer-Customizer/blob/main/README.md .NOTES Run this script as Administrator. #> [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$false,HelpMessage="The path to the JSON configuration file")][String]$Configuration = (Join-Path -Path $PSScriptRoot -ChildPath "ozo-windows-installer-customizer.json"), [Parameter(HelpMessage="Do not clean up the temporary file assets")][Switch]$NoCleanup ) # Classes Class OWICMain { # PROPERTIES: Booleans, Strings, Longs [Boolean] $Validates = $true [Long] $tempFree = $null [String] $jsonPath = $null # PROPERTIES: PSCustomObjects [PSCustomObject] $Json = $null [PSCustomObject] $ozoLogger = (New-OZOLogger) # PROPERTIES: Lists [System.Collections.Generic.List[String]] $Editions = @( "Home", "Home N", "Home Single Language", "Education", "Education N", "Pro", "Pro N", "Pro Education", "Pro Education N", "Pro for Workstations", "Pro N for Workstations", "Enterprise", "Enterprise N", "Enterprise Evaluation", "Enterprise N Evaluation" "Standard Evaluation", "Standard Evaluation (Desktop Experience)", "Datacenter Evaluation", "Datacenter Evaluation (Desktop Experience)" ) [System.Collections.Generic.List[PSCustomObject]] $Jobs = @() # METHODS # Constructor method OWICMain($Configuration,$NoCleanup) { # Set Properties $this.jsonPath = $Configuration # Declare ourselves to the world $this.ozoLogger.Write("Starting process.","Information") # Call ValidateEnvironment to set Validates If (($this.ValidateConfiguration() -And $this.ValidateEnvironment()) -eq $true) { # Iterate through the enabled jobs in the JSON configuration ForEach($jobJson in ($this.Json.Jobs | Where-Object {$_.Enabled -eq $true})) { # Add OscdimgPath, tempDir, and tempFree to the job object Add-Member -InputObject $jobJson -MemberType NoteProperty -Name "OscdimgPath" -Value $this.Json.Paths.OscdimgPath Add-Member -InputObject $jobJson -MemberType NoteProperty -Name "tempDir" -Value $this.Json.Paths.tempDir Add-Member -InputObject $jobJson -MemberType NoteProperty -Name "noCleanup" -Value $NoCleanup Add-Member -InputObject $jobJson -MemberType NoteProperty -Name "tempFree" -Value $this.tempFree # Add this job to the jobs list $this.Jobs.Add(([OWICJob]::new($jobJson))) } # Iterate through the valid jobs ForEach ($Job in ($this.Jobs | Where-Object {$_.Validates -eq $true})) { # Call the CustomizeISO method $Job.CustomizeISO() } } Else { # Configuration and environment do not validate $this.Validates = $false } # Call the report method $this.Report() # Finish $this.ozoLogger.Write("Process complete.","Information") } # JSON validation method Hidden [Boolean] ValidateConfiguration() { # Control variable [Boolean] $Return = $true # Determine if the JSON file exists If ((Test-Path $this.jsonPath) -eq $true) { # File exists; try to convert it from JSON Try { $this.Json = (Get-Content $this.jsonPath -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) # Success } Catch { # Failure $this.ozoLogger.Write("Invalid JSON.","Error") $Return = $false } } Else { # File does not exist $this.ozoLogger.Write("Configuration file does not exists or is not accessible.","Error") $Return = $false } # Return return $Return } # Paths validation method Hidden [Boolean] ValidateEnvironment() { # Control variable [Boolean] $Return = $true # Local variables [String] $tempDrive = $null # Scrutinize TempDir; determine if the path contains spaces If ($this.Json.Paths.TempDir -Match " ") { # Found spaces $this.ozoLogger.Write("Path to temporary directory cannot contain spaces. This is a limitation of the Microsoft oscdimg.exe command.","Error") $Return = $false } # Determine if TempDir exists Try { Test-Path -Path $this.Json.Paths.TempDir -ErrorAction Stop # Success; get statistics $tempDrive = (Get-Item $this.Json.Paths.TempDir).PSDrive.Name $this.tempFree = (Get-Volume -DriveLetter $tempDrive).SizeRemaining # Determine if TempDir drive is fixed If ((Get-Volume -DriveLetter $tempDrive).DriveType -ne "Fixed") { # Disk is not fixed $this.ozoLogger.Write("Temporary directory is not on a fixed drive.","Error") $Return = $false } } Catch { # Path does not exist $this.ozoLogger.Write("Temporary directory does not exist.","Error") $Return = $false } # Determine if oscdimg.exe is missing Try { Test-Path -Path $this.Json.Paths.OscdimgPath -ErrorAction Stop # Success } Catch { # Failure $this.ozoLogger.Write("Did not find oscdimg.exe.","Error") $Return = $false } # Return return $Return } # Report class [Void] Report() { # Determine if any jobs were processed If ($this.Jobs.Count -gt 0) { # At least one job was processed; determine if there were any successes If (($this.Jobs | Where-Object {$_.Success -eq $true}).Count -gt 0) { # At least one job was successful; report names $this.ozoLogger.Write(("Successfully processed the following jobs:`r`n" + (($this.Jobs | Where-Object {$_.Success -eq $true}).Json.Name -Join("`r`n"))),"Information") # Iterate on the success jobs ForEach ($successJob in ($this.Jobs | Where-Object {$_.Success -eq $true})) { $this.ozoLogger.writeOutput = $false $this.ozoLogger.Write(("The " + $successJob.Json.Name + " job succeeded with the following messages:`r`n" + ($successJob.Messages -Join("`r`n"))),"Information") $this.ozoLogger.writeOutput = $true } } # Determine if there were any failures If (($this.Jobs | Where-Object {$_.Success -eq $false}).Count -gt 0) { # At least one job failed; report names $this.ozoLogger.Write(("The following jobs failed:`r`n" + (($this.Jobs | Where-Object {$_.Success -eq $false}).Json.Name -Join("`r`n"))),"Warning") # Iterate ForEach ($failureJob in ($this.Jobs | Where-Object {$_.Success -eq $false})) { $this.ozoLogger.writeOutput = $false $this.ozoLogger.Write(("The " + $failureJob.Json.Name + " job failed with the following messages:`r`n" + ($failureJob.Messages -Join("`r`n"))),"Warning") $this.ozoLogger.writeOutput = $true } } # Report re: additional messages. $this.ozoLogger.Write("Please see the One Zero One Windows Event Provider Log for additional detail. If you have not installed the One Zero One Windows Event Provider, messages can be found in the Microsoft Windows PowerShell Event Log Provider under event ID 4100.","Information") } Else { # No jobs were processed $this.ozoLogger.Write("Processed zero jobs.","Warning") } } } Class OWICJob { # PROPERTES: Boolean, Long, String, PSCUstomObject [Boolean] $Validates = $true [Boolean] $Success = $true [String] $dvdDir = $null [String] $iconFile = $null [String] $jobTempDir = $null [String] $logoFile = $null [String] $mountDir = $null [String] $mountDrive = $null [String] $OscdimgPath = $null [String] $targetISOPath = $null [String] $wallpaperFile = $null [String] $wimDir = $null # PROPERTIES: PSCUstomObject [PSCustomObject] $Json = $null # PROPERTIES: List [System.Collections.Generic.List[String]] $Messages = @() # METHODS # Constructor method OWICJob($Json) { # Set properties $this.Json = $Json $this.jobTempDir = (Join-Path -Path $this.Json.tempDir -ChildPath ((New-Guid).Guid + "-ozo-windows-installer-customizer")) # Declare ourselves to the world $this.Messages.Add("Processing job.") # Call ValidateJob to set Validates If ($this.ValidateJob() -eq $true) { # Job validated $this.Messages.Add("Job validates.") $this.Validates = $true } Else { # Job did not validate $this.Messages.Add("Job does not validate.") $this.Validates = $false $this.Success = $false } } # Validate job method Hidden [Boolean] ValidateJob() { # Control variable [Boolean] $Return = $true # Local variables [Xml] $answerXml = $null # Determine if Name is set If ([String]::IsNullOrEmpty($this.Json.Name) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing Name.") $Return = $false } # Determine if OSName is set If ([String]::IsNullOrEmpty($this.Json.OSName) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing OSName.") $Return = $false } # Determine if Version is set If ([String]::IsNullOrEmpty($this.Json.Version) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing Version.") $Return = $false } # Determine if Edition is set If ([String]::IsNullOrEmpty($this.Json.Edition) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing Edition.") $Return = $false } # Determine if Feature is set If ([String]::IsNullOrEmpty($this.Json.Feature) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing Feature.") $Return = $false } # Determine if Build is set If ([String]::IsNullOrEmpty($this.Json.Build) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing Build.") $Return = $false } # Determine if answerPath is set If ([String]::IsNullOrEmpty($this.Json.Files.answerPath) -eq $true) { # Not set; warn $this.Messages.Add("Job configuration is missing answerPath.") $Return = $false } Else { # Set; Determine that file exists If ((Test-Path -Path $this.Json.Files.answerPath) -eq $true){ # File exists; try to determine if XML is valid Try { $answerXml = [Xml](Get-Content -Path $this.Json.Files.answerPath -ErrorAction Stop) # Success (valid XML); try to get the logo file name Try { $this.logoFile = (Split-Path -Path (($answerXml.unattend.settings | Where-Object {$_.pass -eq "specialize"}).component | Where-Object {$_.name -eq "Microsoft-Windows-Shell-Setup"}).OEMInformation.Logo -Leaf -ErrorAction Stop) # Success } Catch { # Failure $this.Messages.Add("Unable to read logo path from Autounattend XML.") } # Try to get the icon file name Try { $this.iconFile = (Split-Path -Path (($answerXml.unattend.settings | Where-Object {$_.pass -eq "oobeSystem"}).component | Where-Object {$_.name -eq "Microsoft-Windows-Shell-Setup"}).Themes.BrandIcon -Leaf -ErrorAction Stop) # Success } Catch { # Failure $this.Messages.Add("Unable to read icon path from Autounattend XML.") } # Try to get the wallpaper file name Try { $this.wallpaperFile = (Split-Path -Path (($answerXml.unattend.settings | Where-Object {$_.pass -eq "oobeSystem"}).component | Where-Object {$_.name -eq "Microsoft-Windows-Shell-Setup"}).Themes.DesktopBackground -Leaf -ErrorAction SilentlyContinue) # Success } Catch { # Failure $this.Messages.Add("Unable to read wallpaper path from Autounattend XML.") } } Catch { # Failure (invalid XML) $this.Messages.Add("Answer file contains invalid XML.") $Return = $false } } Else { # File does not exist; error $this.Messages.Add("Answer file specified but file not found.") $Return = $false } } # Determine if sourceISOPath is set If ([String]::IsNullOrEmpty($this.Json.Files.sourceISOPath) -eq $true) { # Not set; error $this.Messages.Add("Job configuration is missing sourceISOPath.") } Else { # Set; determine that path does not contain spaces If ($this.Json.Files.sourceISOPath -Match " ") { # Path contains spaces - oscdimg.exe cannot handle spaces $this.Messages.Add("The sourceISOPath contains spaces. Due to a limitation with oscdimg.exe, this path cannot contain spaces.") $Return = $false } # Determine that file exists If ((Test-Path -Path $this.Json.Files.sourceISOPath) -eq $true) { # File exists; parse to set targetISOPath $this.targetISOPath = (Join-Path -Path (Split-Path -Path $this.Json.Files.sourceISOPath -Parent) -ChildPath ("OZO-" + $this.Json.OSName + "-" + $this.Json.Version + "-" + $this.Json.Edition + "-" + $this.Json.Feature + "-" + $this.Json.Build + ".iso")) # Determine if target ISO already exists If ((Test-Path -Path $this.targetISOPath) -eq $true) { # Target ISO exists; error $this.Messages.Add("Target ISO exists; skipping.") $Return = $false } # File exists; determine if the disk has enough space for this job If ($this.Json.tempFree -lt ((Get-Item -Path $this.Json.Files.sourceISOPath).Length * 4)) { # Disk does not have enough space $this.Messages.Add("Drive does not have enough free space; requires size of ISO x 4.") $Return = $false } } Else { # File does not exist; error $this.Messages.Add("Source ISO specified but file not found.") $Return = $false } } # Determine if logoPath is set If ([String]::IsNullOrEmpty($this.Json.Files.logoPath) -eq $false) { # Set; determine that file exists If((Test-Path -Path $this.Json.Files.logoPath) -eq $false) { # File does not exist; warn $this.Messages.Add("Logo path specified but file not found.") $Return = $false } } # Determine if iconPath is set If ([String]::IsNullOrEmpty($this.Json.Files.iconPath) -eq $false) { # Set; determine that file exists If((Test-Path -Path $this.Json.Files.iconPath) -eq $false) { # File does not exist; warn $this.Messages.Add("Icon path specified but file not found.") $Return = $false } } # Determine if wallpaperPath is set If ([String]::IsNullOrEmpty($this.Json.Files.wallpaperPath) -eq $false) { # Set; determine that file exists If((Test-Path -Path $this.Json.Files.wallpaperPath) -eq $false) { # File does not exist; warn $this.Messages.Add("Wallpaper path specified but file not found.") $Return = $false } } # Determine if any drivers directories are set If ($this.Json.Drivers.Count -gt 0) { # At least one drivers directory is set; iterate ForEach ($Driver in $this.Json.Drivers) { # Determine if the path is not found If ((Test-Path -Path $Driver) -eq $false) { # Path is not found $this.Messages.Add(("Missing drivers directory " + $Driver + ".")) $Return = $false } } } Else { # No drivers directories are set $this.Messages.Add("No drivers directories specified.") } # Determine if any AppXProvisionedPackages are set If($this.Json.removeAppxProvisionedPackages.Count -eq 0) { # No AppXPackages set $this.Messages.Add("No AppxPackages specified.") } # Return return $Return } # Customize ISO method [Void] CustomizeISO() { # Call all methods in order to set Success $this.Success = ( $this.CreateJobTempDirs() -And $this.MountISO() -And $this.CopyISO() -And $this.MoveWIM() -And $this.ExportIndex() -And $this.MountWIM() -And $this.CopyMediaAssets() -And $this.AddDrivers() -And $this.RemoveAppxPackages() -and $this.DismountWIM() -And $this.CopyAnswerFile() -And $this.WriteISO() ) # Call Cleanup to clean up temporary file assets $this.Cleanup() } # Create job temporary directories method Hidden [Boolean] CreateJobTempDirs() { # Control variable [Boolean] $Return = $true # Attempt to create jobTempDir Try { New-Item -ItemType Directory -Path $this.jobTempDir -ErrorAction Stop # Success; set paths $this.dvdDir = (Join-Path -Path $this.jobTempDir -ChildPath "DVD") $this.wimDir = (Join-Path -Path $this.jobTempDir -ChildPath "WIM") $this.mountDir = (Join-Path -Path $this.jobTempDir -ChildPath "Mount") # Try to create paths Try { New-Item -ItemType Directory -Path $this.dvdDir -Force -ErrorAction Stop New-Item -ItemType Directory -Path $this.wimDir -Force -ErrorAction Stop New-Item -ItemType Directory -Path $this.mountDir -Force -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add(("Unable to create one or more subdirectories of " + $this.jobTempDir)) $Return = $false } } Catch { # Failure $this.Messages.Add("Failed to create temporary job directory") $Return = $false } # Return return $Return } # Mount ISO method Hidden [Boolean] MountISO() { # Control variable [Boolean] $Return = $true Try { Mount-DiskImage -ImagePath $this.Json.Files.sourceISOPath -ErrorAction Stop # Success; get the drive letter $this.mountDrive = (Get-DiskImage $this.Json.Files.sourceISOPath -ErrorAction Stop | Get-Volume -ErrorAction Stop).DriveLetter } Catch { # Failure $this.Messages.Add("Failed to mount source ISO") $Return = $false } # Return return $Return } # Copy ISO method Hidden [Boolean] CopyISO() { # Control variable [Boolean] $Return = $true Try { Copy-Item -Path ($this.mountDrive + ":\*") -Recurse -Destination ($this.dvdDir + "\") -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to copy ISO contents to the DVD directory") $Return = $false } # Return return $Return } # Move WIM method Hidden [Boolean] MoveWIM() { # Control variable [Boolean] $Return = $true Try { Move-Item -Path (Join-Path -Path $this.dvdDir -ChildPath "sources\install.wim") -Destination ($this.wimDir + "\") -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to move the WIM") $Return = $false } # Return return $Return } # Export index method Hidden [Boolean] ExportIndex() { # Control variable [Boolean] $Return = $true [String] $IndexName = ($this.Json.OSName + " " + $this.Json.Version + " " + $this.Json.Edition) Try { Export-WindowsImage -SourceName $IndexName -SourceImagePath (Join-Path -Path $this.wimDir -ChildPath "install.wim") -DestinationImagePath (Join-Path -Path $this.dvdDir -ChildPath "sources\install.wim") -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add(("Failed to export the " + $IndexName + " index")) $Return = $false } # Return return $Return } # Mount WIM method Hidden [Boolean] MountWIM() { # Control variable [Boolean] $Return = $true Try { Mount-WindowsImage -Path $this.mountDir -ImagePath (Join-Path -Path $this.dvdDir -ChildPath "sources\install.wim") -Index 1 -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to mount WIM") $Return = $false } # Return return $Return } # Copy media assets method Hidden [Boolean] CopyMediaAssets() { # Control variable [Boolean] $Return = $true # Determine if any of the media files are set If ([String]::IsNullOrEmpty($this.logoFile) -eq $false -Or [String]::IsNullOrEmpty($this.iconFile) -eq $false -Or [String]::IsNullOrEmpty($this.wallpaperFile) -eq $false) { # Try to create the OEM directory Try { New-Item -ItemType Directory -Path (Join-Path -Path $this.mountDir -ChildPath "Windows\System32\OEM") -ErrorAction Stop # Success; determine if logoPath was set in the XML If ([String]::IsNullOrEmpty($this.logoFile) -eq $false) { # logoPath was set in the XML; determine if the logoPath is set in the job configuration and the file exists If ([String]::IsNullOrEmpty($this.Json.Files.logoPath) -eq $false -And (Test-Path -Path $this.Json.Files.logoPath) -eq $true) { # logoPath is set in the job configuration and the file exists; try to copy Try { Copy-Item -Path $this.Json.Files.logoPath -Destination (Join-Path -Path $this.mountDir -ChildPath (Join-Path -Path "Windows\System32\OEM" -ChildPath $this.logoFile)) -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to copy logo file.") $Return = $false } } Else { $this.Messages.Add("Logo is specified in the Autounattend XML but (a) not specified in the job configuration or (b) specified in the job configuration but the file is missing or not accessible.") $Return = $false } } Else { $this.Messages.Add("No logo path found in the Autounattend XML.") } # Determine if iconPath is set If ([String]::IsNullOrEmpty($this.iconFile) -eq $false) { # iconPath was set in the XML; determine if the iconPath is set in the job configuration and the file exists If ([String]::IsNullOrEmpty($this.Json.Files.iconPath) -eq $false -And (Test-Path -Path $this.Json.Files.iconPath) -eq $true) { # logoPath is set in the job configuration and the file exists; try to copy Try { Copy-Item -Path $this.Json.Files.iconPath -Destination (Join-Path -Path $this.mountDir -ChildPath (Join-Path -Path "Windows\System32\OEM" -ChildPath $this.iconFile)) -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to copy icon file.") $Return = $false } } Else { $this.Messages.Add("Icon is specified in the Autounattend XML but (a) not specified in the job configuration or (b) specified in the job configuration but the file is missing or not accessible.") $Return = $false } } Else { $this.Messages.Add("No icon path found in the Autounattend XML.") } # Determine if wallpaperPath is set If ([String]::IsNullOrEmpty($this.wallpaperFile) -eq $false) { # wallpaperPath was set in the XML; determine if the wallpaperPath is set in the job configuration and the file exists If ([String]::IsNullOrEmpty($this.Json.Files.wallpaperPath) -eq $false -And (Test-Path -Path $this.Json.Files.wallpaperPath) -eq $true) { # logoPath is set in the job configuration and the file exists; try to copy Try { Copy-Item -Path $this.Json.Files.wallpaperPath -Destination (Join-Path -Path $this.mountDir -ChildPath (Join-Path -Path "Windows\System32\OEM" -ChildPath $this.wallpaperFile)) -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to copy logo file.") $Return = $false } } Else { $this.Messages.Add("Wallpaper is specified in the Autounattend XML but (a) not specified in the job configuration or (b) specified in the job configuration but the file is missing or not accessible.") $Return = $false } } Else { $this.Messages.Add("No wallpaper path found in the Autounattend XML.") } } Catch { $this.Messages.Add("Unable to create OEM directory for media assets.") $Return = $false } } Else { # No media files are set $this.Messages.Add("No media files specified.") } # Return return $Return } # Add drivers method Hidden [Boolean] AddDrivers() { # Control variable [Boolean] $Return = $true # Iterate through the Models ForEach ($Driver in $this.Drivers) { # Try to add drivers Try { Add-WindowsDriver -Path $this.mountDir -Driver $Driver -Recurse -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add(("Failed to add " + $Driver + " to image.")) $Return = $false } } # Return return $Return } # RemoveAppxPackages method Hidden [Boolean] RemoveAppxPackages() { # Control variable [Boolean] $Return = $true # Determine if there are any packages to be removed If ($this.Json.removeAppxProvisionedPackages.Count -gt 0) { # One or more packages have been identified for removal; iterate through them ForEach ($AppxPackage in (Get-AppxProvisionedPackage -Path $this.mountDir)) { # Determine if this package appears in the image If ($this.removeAppxProvisionedPackages -Contains $AppxPackage.DisplayName) { # Package appears in the image; try to remove Try { Remove-AppXProvisionedPackage -Path $this.mountDir -PackageName $AppxPackage.PackageName -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add(("Unable to remove the " + $AppxPackage.DisplayName + " package from the image.")) } } } } Else { $this.Messages.Add("No AppxPackages to remove.") } # Return return $Return } # Unmount WIM method Hidden [Boolean] DismountWIM() { # Control variable [Boolean] $Return = $true # Try to dismount the WIM Try { Dismount-WindowsImage -Path $this.mountDir -Save -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to dismount the WIM.") $Return = $false } # Return return $Return } # Copy answer file method Hidden [Boolean] CopyAnswerFile() { # Control variable [Boolean] $Return = $true Try { Copy-Item -Path $this.Json.Files.answerPath -Destination ($this.dvdDir + "\Autounattend.xml") -ErrorAction Stop #Success } Catch { # Failure $this.Messages.Add("Failed to copy Answer file.") $Return = $false } # Return return $Return } # Write ISO method Hidden [Boolean] WriteISO() { # Control variable [Boolean] $Return = $true # Try to write the ISO Try { Start-Process -NoNewWindow -Wait -FilePath $this.Json.OscdimgPath -ArgumentList ('-u2 -udfver102 -t -l' + [System.IO.Path]::GetFileNameWithoutExtension($this.targetISOPath) + ' -b' + (Join-Path -Path $this.dvdDir -ChildPath "efi\microsoft\boot\efisys.bin") + ' ' + ($this.dvdDir + "\") + ' ' + $this.targetISOPath) #Success } Catch { # Failure $this.Messages.Add("Failed to write ISO.") $Return = $false } # Return return $Return } # Cleanup method Hidden [Void] Cleanup() { # Determine if there are any mounted images If ((Get-WindowsImage -Mounted) -Contains $this.mountDir) { # There are mounted images and one of them contains mountDir; try to unmount Try { Dismount-WindowsImage -Path $this.MountDir -Discard -ErrorAction Stop # Success; } Catch { $this.Messages.Add("Unable to unmount image.") } } # Determine if the ISO is still mounted If ($null -ne (Get-DiskImage $this.Json.Files.sourceISOPath | Get-Volume).DriveLetter) { # ISO is still mounted; try to dismount Try { Dismount-DiskImage -ImagePath $this.Json.Files.sourceISOPath -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add("Unable to unmount ISO.") } } # Determine if operator did not request NoCleanup If ($this.Json.noCleanup -eq $false) { # Operator did not request NoCleanup; try to remove the jobTempDir Try { Remove-Item -Recurse -Force -Path $this.jobTempDir -ErrorAction Stop } Catch { $this.Messages.Add(("Unable to remove job temporary directory " + $this.jobTempDir + ". Please delete manually.")) } } } } # MAIN [OWICMain]::new($Configuration,$NoCleanup) | Out-Null # SIG # Begin signature block # MIIfcQYJKoZIhvcNAQcCoIIfYjCCH14CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB17kiY3PcmRw9f # S0WKdkQblG32YGKn6TN1agojR00mmaCCDPgwggZyMIIEWqADAgECAghkM1HTxzif # CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx # EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G # A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe # Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w # DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv # cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD # QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG # bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU # Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX # 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC # GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB # wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk # jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X # gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg # Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge # VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR # 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV # t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF # MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH # AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE # CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g # LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G # A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ # KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH # 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x # xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT # V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P # Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc # +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ # LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV # HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3 # NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9 # XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz # iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od # MIIGfjCCBGagAwIBAgIQZ2iSsNbwOsjnLExSAX6F6DANBgkqhkiG9w0BAQsFADB4 # MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x # ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu # ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI0MTExNjEwMzUyOFoXDTI1MTEx # NjEwMzUyOFowZTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYD # VQQHDAZEZW52ZXIxGDAWBgNVBAoMD0FuZHJldyBMaWV2ZXJ0ejEYMBYGA1UEAwwP # QW5kcmV3IExpZXZlcnR6MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA # vIBAQzK0aahepOrPmvCEqfd6dMZC4GvV7kflKwrn4QPJGfqhFmUtadP1e3ange8O # QZ3/w7UjOTAUNUHfhjbSgUBlKjbS6EWQKZuRFzI3SNkMJkcjTX4uS2P4QsnwM+SW # IE5me3CTssdjtgue+Iiy53TMgW8JpoxiULVxmm3bhCRUAgxWeT6tzjytR1UyGcMc # cm/YE6TOgsCHiZoo4X4HJD9iHDrNldArq04Jl6FsADxEswttKyfqpIRJLoAysVl1 # f8CEDBwhszJrEXBnAlWViJFfNY+dKP4jhf7lLqSvPCuADqP2jvM0Ym5I8qDGMz9j # XPSMLF58MFB4vM4viS7nLRFJ8S1Q98vQvB8W4kk0WPuiZbZTHsROzohE1VSbLnIY # ag5dDOWI8L6yutAsfdZFYFmSTKcMSiOj5VbK4LhAJUL2G8vPwpTGFgr+cEp0p62F # P0WXK+/cRfGqodI5S+bg+9rQTD9zf829DwraSRAt5P5zrQk4WPst3JW/vIKNx7cV # AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFTC/hCVAJPN # avXnwNfZsku4jwzjMHoGCCsGAQUFBwEBBG4wbDBIBggrBgEFBQcwAoY8aHR0cDov # L2NlcnQuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQwOTYt # UjEuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTBRBgNVHSAE # SjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEwLDAqBggrBgEFBQcCARYeaHR0 # cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMD # ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMY29tLVN1 # YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNybDAdBgNVHQ4EFgQUSj8HrSK7 # f/j+Dz31jJFhOF7rJUMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IC # AQBf4lcc6FUJ1W/opNz8yjS9qLUy9cQt0s35BhasB5QoTbDaW4jv9xnFGhQVg6n+ # jhL0i94Vsywd/MRBb8lYGpuBZnS/7LHuRZu7qUuud+IMDyRHIyBK6koN5bfyA5VY # c7bFbNpbe1s1hMWke8di4qgMLZKDfyG/RtA0swf5t4UgQLPP0h+koZ8X8V5+P0V0 # 1HsdXyXd+ojo38EoZyCKfQL2aAwMPwzZfCbmI5SRXNOc6K8oqXzQcendhlKSfVBo # Zgpi+1updqbD4jmJfYdK5AYPxJ3YH6td6ETtr8owL+bmX8lQjlXPOwVnC11rVlNB # VjqtaJRUClLtiNiYSTKVfjdmGVJ4+sNov0dWhHc0A9o5NX/05VVYTlImuJpnG5Og # o7w6kWRdsgE8gM58jWf7XfI6aQS0Np/z2B+ZBj0K93khEHBX7cvvORa92LCHiVeP # km+zEAMXgxIPs/e8cmcc/o3CORgzEwxlH9Z3UOWCuXSHD3P2RPNDAY+WPdjSHm9f # JFlGq+f9iKyedxYa/NNjNag/5EbZ+Z2NldtSMNeFdsejGJ/TJHF1PyJd4aXx9J1i # B/IZBOoJYyh9xpQ3ljZUKE/4otPi7INpuDFwgWiUHZZJVvrGTWwxH1Yhf8P+VpFf # aNqsBuvklUcUDs3RNE0f1qlgFfcnAepFF+RiBRqmsj29fjGCEc8wghHLAgEBMIGM # MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv # bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu # aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ # YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG # 9w0BCQQxIgQgs3mQ55T6Vgr8SxXFactBQIBClyO/LN2wttO/jRQyWkswDQYJKoZI # hvcNAQEBBQAEggGAMvGSbPYVy1oPFWvJqnrhoLBo9/GypkTt9k9CPxqG4zPEUAS7 # 8hvaz+/vI5Wsev8PuQRYeDcT2aDKq3LfbtTtyPmhzNXZqgBYgwTGL1XgLCI4n5nm # oPQD2KizyzuGuNHwp+sipD4DhKqJQ58Ym4ZiRXkN4RZ1jw/ldyn9PBwPxfGKcZjp # H6VgGbC4NvAesX5wuSNDeN1t5lMeW617GjVwy1Gmn3+g49OVJH0GWYX61vaRSf1V # mZtdNeTfcI5PknL4kRvl4yZzXJQInpOFPsdwxrVMkxBf1C8hjAEGwNWzBmKgGglH # zkD0cC5dXFLntP42TUvVYmki97Oahof+1B5LvNn49CUChpjucn/7ZMN7edomgmng # q5eJefNYZnUD2SgaTw12XytxC3kBs5ZZzwYVqVEPftJDVa4zPvChSjAdCOt7nlnQ # TdyseBRt9zRw1wuIdcgiQSiE1+UUJC5pFEIBm/5TAQAD5eEXFs6pUUPnkpymy+Z0 # NGpvpPpcyDtWjLRooYIPFTCCDxEGCisGAQQBgjcDAwExgg8BMIIO/QYJKoZIhvcN # AQcCoIIO7jCCDuoCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm # MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCCKud7k5tkwSFsI # 8DHpQkZIJTD97fDxNab5PJQX/pMkDAIIMisRpj4+79YYDzIwMjUwNDA2MDIwMjM2 # WjADAgEBoIIMADCCBPwwggLkoAMCAQICEFparOgaNW60YoaNV33gPccwDQYJKoZI # hvcNAQELBQAwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQH # DAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBU # aW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjEwHhcNMjQwMjE5MTYxODE5WhcN # MzQwMjE2MTYxODE4WjBuMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO # BgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMSowKAYDVQQDDCFTU0wu # Y29tIFRpbWVzdGFtcGluZyBVbml0IDIwMjQgRTEwWTATBgcqhkjOPQIBBggqhkjO # PQMBBwNCAASnYXL1MOl6xIMUlgVC49zonduUbdkyb0piy2i8t3JlQEwA74cjK8g9 # mRC8GH1cAAVMIr8M2HdZpVgkV1LXBLB8o4IBWjCCAVYwHwYDVR0jBBgwFoAUDJ0Q # JY6apxuZh0PPCH7hvYGQ9M8wUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVo # dHRwOi8vY2VydC5zc2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIx # LmNlcjBRBgNVHSAESjBIMDwGDCsGAQQBgqkwAQMGATAsMCoGCCsGAQUFBwIBFh5o # dHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkwCAYGZ4EMAQQCMBYGA1UdJQEB # /wQMMAoGCCsGAQUFBwMIMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jcmxzLnNz # bC5jb20vU1NMLmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY3JsMB0GA1UdDgQW # BBRQTySs77U+YxMjCZIm7Lo6luRdIjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcN # AQELBQADggIBAJigjwMAkbyrxGRBf0Ih4r+rbCB57lTuwViC6nH2fZSciMogpqSz # rSeVZ2eIb5vhj9rT7jqWXZn02Fncs4YTrA1QyxJW36yjC4jl5/bsFCaWuXzGXt2Y # 6Ifp//A3Z0sNTMWTTBobmceM3sqnovdX9ToRFP+29r5yQnPcgRTI2PvrVSqLxY9E # yk9/0cviM3W29YBl080ENblRcu3Y8RsfzRtVT/2snuDocRxvRYmd0TPaMgIj2xII # 651QnPp1hiq9xU0AyovLzbsi5wlR5Ip4i/i8+x+HwYJNety5cYtdWJ7uQP6YaZtW # /jNoHp76qNftq/IlSx6xEYBRjFBxHSq2fzhUQ5oBawk2OsZ2j0wOf7q7AqjCt6t/ # +fbmWjrAWYWZGj/RLjltqdFPBpIKqdhjVIxaGgzVhaE/xHKBg4k4DfFZkBYJ9BWu # P93Tm+paWBDwXI7Fg3alGsboErWPWlvwMAmpeJUjeKLZY26JPLt9ZWceTVWuIyuj # erqb5IMmeqLJm5iFq/Qy4YPGyPiolw5w1k9OeO4ErmS2FKvk1ejvw4SWR+S1VyWn # ktY442WaoStxBCCVWZdMWFeB+EpL8uoQNq1MhSt/sIUjUudkyZLIbMVQjj7b6gPX # nD6mS8FgWiCAhuM1a/hgA+6o1sJWizHdmcpYDhyNzorf9KVRE6iR7rcmMIIG/DCC # BOSgAwIBAgIQbVIYcIfoI02FYADQgI+TVjANBgkqhkiG9w0BAQsFADB8MQswCQYD # VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNV # BAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp # ZmljYXRpb24gQXV0aG9yaXR5IFJTQTAeFw0xOTExMTMxODUwMDVaFw0zNDExMTIx # ODUwMDVaMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwH # SG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGlt # ZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEArlEQE9L5PCCgIIXeyVAcZMnh/cXpNP8KfzFI6HJaxV6oYf3x # h/dRXPu35tDBwhOwPsJjoqgY/Tg6yQGBqt65t94wpx0rAgTVgEGMqGri6vCI6rEt # SZVy9vagzTDHcGfFDc0Eu71mTAyeNCUhjaYTBkyANqp9m6IRrYEXOKdd/eREsqVD # mhryd7dBTS9wbipm+mHLTHEFBdrKqKDM3fPYdBOro3bwQ6OmcDZ1qMY+2Jn1o0l4 # N9wORrmPcpuEGTOThFYKPHm8/wfoMocgizTYYeDG/+MbwkwjFZjWKwb4hoHT2WK8 # pvGW/OE0Apkrl9CZSy2ulitWjuqpcCEm2/W1RofOunpCm5Qv10T9tIALtQo73GHI # lIDU6xhYPH/ACYEDzgnNfwgnWiUmMISaUnYXijp0IBEoDZmGT4RTguiCmjAFF5OV # NbY03BQoBb7wK17SuGswFlDjtWN33ZXSAS+i45My1AmCTZBV6obAVXDzLgdJ1A1r # yyXz4prLYyfJReEuhAsVp5VouzhJVcE57dRrUanmPcnb7xi57VPhXnCuw26hw1Hd # +ulK3jJEgbc3rwHPWqqGT541TI7xaldaWDo85k4lR2bQHPNGwHxXuSy3yczyOg57 # TcqqG6cE3r0KR6jwzfaqjTvN695GsPAPY/h2YksNgF+XBnUD9JBtL4c34AcCAwEA # AaOCAYEwggF9MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU3QQJB6L1 # en1SUxKSle44gCUNplkwgYMGCCsGAQUFBwEBBHcwdTBRBggrBgEFBQcwAoZFaHR0 # cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tUm9vdENlcnRpZmljYXRp # b25BdXRob3JpdHlSU0EuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3Ns # LmNvbTA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cHM6Ly93 # d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMIMDsGA1Ud # HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5jb20vc3NsLmNvbS1yc2EtUm9v # dENBLmNybDAdBgNVHQ4EFgQUDJ0QJY6apxuZh0PPCH7hvYGQ9M8wDgYDVR0PAQH/ # BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSGXUNplpCzxkH2fL8lPrAm/AV6USW # Wi9xM91Q5RN7mZN3D8T7cm1Xy7qmnItFukgdtiUzLbQokDJyFTrF1pyLgGw/2hU3 # FJEywSN8crPsBGo812lyWFgAg0uOwUYw7WJQ1teICycX/Fug0KB94xwxhsvJBiRT # pQyhu/2Kyu1Bnx7QQBA1XupcmfhbQrK5O3Q/yIi//kN0OkhQEiS0NlyPPYoRboHW # C++wogzV6yNjBbKUBrMFxABqR7mkA0x1Kfy3Ud08qyLC5Z86C7JFBrMBfyhfPpKV # lIiiTQuKz1rTa8ZW12ERoHRHcfEjI1EwwpZXXK5J5RcW6h7FZq/cZE9kLRZhvnRK # tb+X7CCtLx2h61ozDJmifYvuKhiUg9LLWH0Or9D3XU+xKRsRnfOuwHWuhWch8G7k # EmnTG9CtD9Dgtq+68KgVHtAWjKk2ui1s1iLYAYxnDm13jMZm0KpRM9mLQHBK5Gb4 # dFgAQwxOFPBslf99hXWgLyYE33vTIi9p0gYqGHv4OZh1ElgGsvyKdUUJkAr5hfbD # X6pYScJI8v9VNYm1JEyFAV9x4MpskL6kE2Sy8rOqS9rQnVnIyPWLi8N9K4GZvPit # /Oy+8nFL6q5kN2SZbox5d69YYFe+rN1sDD4CpNWwBBTI/q0V4pkgvhL99IV2Xasj # HZf4peSrHdL4RjGCAlcwggJTAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI # DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt # BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa # WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA0MDYwMjAyMzZaMCgGCSqG # SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ # BDEiBCC8gbAaavd8JQlxjGk1pz3MYnz2NWYoy1I1kFWi9x67LzCByQYLKoZIhvcN # AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29 # iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV # BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t # IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9 # xzAKBggqhkjOPQQDAgRGMEQCICQeKn9Cltjzyldw+dnygM/8SZ6CQoOMy0Bnhtuu # OR1iAiBtqJoJE8jtn64ewlN3Ly5bgHoR2x7qjdbvp4ZKNRdfVg== # SIG # End signature block |