ozo-ad-lab-customize-isos.ps1
#Requires -Modules @{ModuleName="OZO";ModuleVersion="1.5.1"},OZOLogger -Version 5.1 <#PSScriptInfo .VERSION 0.0.1 .GUID 107291d5-0444-4f57-b173-8578b222576b .AUTHOR Andy Lievertz <alievertz@sonichealthcareusa.com> .COMPANYNAME One Zero One .COPYRIGHT This script is released under the terms of the GNU General Public License ("GPL") version 2.0. .TAGS .LICENSEURI https://github.com/onezeroone-dev/OZO-AD-Lab-Customize-ISOs/blob/main/LICENSE .PROJECTURI https://github.com/onezeroone-dev/OZO-AD-Lab-Customize-ISOs .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES https://github.com/onezeroone-dev/OZO-AD-Lab-Customize-ISOs/blob/main/CHANGELOG.md #> <# .SYNOPSIS See description. .DESCRIPTION Leverages resources from the One Zero One AD Lab release to customize AlmaLinux and Microsoft installer ISOs. .PARAMETER OZOADLabDir Location of the "ozo-ad-lab" directory. Defaults to $Env:SystemDrive\ozo-ad-lab. .LINK https://github.com/onezeroone-dev/OZO-AD-Lab-Customize-ISOs/blob/main/README.md #> # PARAMETERS [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory=$false,HelpMessage="Path to the OZO AD Lab directory")][String]$OZOADLabDir = (Join-Path -Path $Env:SystemDrive -ChildPath "ozo-ad-lab") ) # CLASSES Class OZOADLCIMain { # PROPERTIES: Strings [String] $downloadsDirectory = $null [String] $ozoADLabDirectory = $null # PROPERTIES: PSCustomObjects [PSCustomObject] $ISO = $null [PSCustomObject] $Logger = $null # METHODS # Constructor method OZOADLCIMain($OZOADLabDir) { # Set properties $this.downloadsDirectory = (Join-Path -Path $Env:USERPROFILE -ChildPath "Downloads") $this.ozoADLabDirectory = $OZOADLabDir # Create a logger object $this.Logger = (New-OZOLogger) # Log a process start message $this.Logger.Write("Process starting.","Information") # Router $this.Logger.Write("Processing the Router ISO.","Information") $this.ISO = [OZOADLCIISO]::new( "Router", #Build "AlmaLinux-9-5-x86_64-dvd", #CustomISOLabel (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Linux"), #CustomISOLinuxDir (Join-Path -Path $this.downloadsDirectory -ChildPath "OZO-AD-Lab-Router.iso"), #CustomISOMOvePath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "OZO-AD-Lab-Router.iso"), #CustomISOOutputPath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\almalinux-boot.iso") #SourceISOPath ) $this.Logger.Write(("Results: " + ($this.ISO.Messages -Join("`r`n"))),"Information") # Client $this.Logger.Write("Processing the Client ISO.","Information") $this.ISO = [OZOADLCIISO]::new( "Client", #Build (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Builds\Client"), #CustomISOBUildDir "OZO-AD-Lab-Client", #CustomISOLabel (Join-Path -Path $this.downloadsDirectory -ChildPath "OZO-AD-Lab-Client.iso"), #CustomISOMovePath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Mount"), #CustomISOMountDir (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\OZO-AD-Lab-Client.iso"), #CustomISOOutputPath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "WIM\Windows 11 Enterprise"), #CustomWIMDir 1, #SourceIndex (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\microsoft-windows-11-enterprise-evaluation.iso"), #SourceISOPath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\microsoft-windows-11-laof.iso") #SourceLAoFISOPath ) $this.Logger.Write(("Results: " + ($this.ISO.Messages -Join("`r`n"))),"Information") # DC $this.Logger.Write("Processing the DC ISO.","Information") $this.ISO = [OZOADLCIISO]::new( "DC",# Build (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Builds\DC"), #CustomISOBuildDir "OZO-AD-Lab-DC", #CustomISOLabel (Join-Path -Path $this.downloadsDirectory -ChildPath "OZO-AD-Lab-DC.iso"), #CustomISOMovePath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Mount"), #CustomISOMountDir (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\OZO-AD-Lab-DC.iso"), #CustomISOOutputPath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "WIM\Windows Server 2022"), #CustomWIMDir 2, #SourceIndex (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\microsoft-windows-server-2022-evaluation.iso"), #SourceISOPath $null #SourceLAoFISOPath ) $this.Logger.Write(("Results: " + ($this.ISO.Messages -Join("`r`n"))),"Information") # Server $this.Logger.Write("Processing the Server ISO.","Information") $this.ISO = [OZOADLCIISO]::new( "Server",# Build (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Builds\Server"), #CustomISOBuildDir "OZO-AD-Lab-DC", #CustomISOLabel (Join-Path -Path $this.downloadsDirectory -ChildPath "OZO-AD-Lab-Server.iso"), #CustomISOMovePath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "Mount"), #CustomISOMountDir (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\OZO-AD-Lab-Server.iso"), #CustomISOOutputPath (Join-Path -Path $this.ozoADLabDirectory -ChildPath "WIM\Windows Server 2022"), #CustomWIMDir 2, #SourceIndex (Join-Path -Path $this.ozoADLabDirectory -ChildPath "ISO\microsoft-windows-server-2022-evaluation.iso"), #SourceISOPath $null #SourceLAoFISOPath ) $this.Logger.Write(("Results: " + ($this.ISO.Messages -Join("`r`n"))),"Information") # Log a process end message $this.Logger.Write("Process complete.","Information") } } Class OZOADLCIISO { # PROPERTIES: Booleans, Int16s, Strings [Boolean] $Proceed = $true [Boolean] $Validates = $true [Int16] $sourceIndex = $null [String] $Build = $null [String] $customISOBuildDir = $null [String] $customISOLabel = $null [String] $customISOLinuxDir = $null [String] $customISOMovePath = $null [String] $customISOMountDir = $null [String] $customISOOutputPath = $null [String] $customWIMDir = $null [String] $efisysBinPath = $null [String] $mountDrive = $null [String] $oscdimgExePath = $null [String] $sourceISOPath = $null [String] $sourceLAoFISOPath = $null # PROPERTIES: Lists [System.Collections.Generic.List[String]] $Messages = @() # METHODS # Constructor method - Linux overload OZOADLCIISO($Build,$CustomISOLabel,$CustomISOLinuxDir,$CustomISOMovePath,$CustomISOOutputPath,$SourceISOPath) { # Set properties $this.Build = $Build $this.customISOLabel = $CustomISOLabel $this.customISOLinuxDir = $CustomISOLinuxDir $this.customISOMovePath = $CustomISOMovePath $this.customISOOutputPath = $CustomISOOutputPath $this.oscdimgExePath = (Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe") $this.sourceISOPath = $SourceISOPath # Determine if ISO validates If ($this.ValidateLinISO() -eq $true) { # ISO validates; call ProcessLinISO to create the custom ISO $this.ProcessLinISO() } } # Constructor method - Windows overload OZOADLCIISO($Build,$CustomISOBuildDir,$CustomISOLabel,$CustomISOMovePath,$CustomISOMountDir,$CustomISOOutputPath,$CustomWIMDir,$SourceIndex,$SourceISOPath,$SourceLAoFISOPath) { # Set properties $this.Build = $Build $this.customISOBuildDir = $CustomISOBuildDir $this.customISOLabel = $CustomISOLabel $this.customISOMovePath = $CustomISOMovePath $this.customISOMountDir = $CustomISOMountDir $this.customISOOutputPath = $CustomISOOutputPath $this.customWIMDir = $CustomWIMDir $this.efisysBinPath = (Join-Path -Path $this.customISOBuildDir -ChildPath "efi\microsoft\boot\efisys.bin") $this.oscdimgExePath = (Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe") $this.sourceIndex = $SourceIndex $this.sourceISOPath = $SourceISOPath $this.sourceLAoFISOPath = $SourceLAoFISOPath # Determine if ISO validates IF ($this.ValidateWinISO() -eq $true) { # ISO validates; call ProcessWinISO to create the custom ISO $this.ProcessWinISO() } } # Validate Linux ISO method Hidden [Boolean] ValidateLinISO() { # Control variable [Boolean] $Return = $true # Check if WSL Debian is installed If ([Boolean](wsl -l | Where-Object {$_.Replace("`0","") -Match '^Debian'}) -eq $false) { # Did not find WSL Debian $this.Messages.Add("Missing WSL Debian. Please see https://onezeroone.dev/active-directory-lab-part-ii-customization-prerequisites/ for more information.") $Return = $false } # Determine if custom ISO already exists in Downloads directory If ((Test-Path -Path $this.customISOMovePath) -eq $true) { # Custom ISO already exists $this.Messages.Add(("Found " + $this.customISOMovePath + "; skipping.")) $Return = $false } # Determine if custom ISO already exists in OZO AD Lab directory If ((Test-Path -Path $this.customISOOutputPath) -eq $true) { # Custom ISO already exists $this.Messages.Add(("Found " + $this.customISOOutputPath + "; skipping.")) $Return = $false } # Determine that source ISO does not exist If ((Test-Path -Path $this.sourceISOPath) -eq $false) { # Source ISO does not exist $this.Messages.Add(("Missing " + $this.sourceISOPath + "; skipping.")) $Return = $false } # Return return $Return } # Process Linux ISO method Hidden [Void] ProcessLinISO() { # Local variables [String] $wslResult = $null [String] $wslKickstartPath = (New-OZOWSLPathFromWindowsPath -WindowsPath (Join-Path -Path $this.customISOLinuxDir -ChildPath "ozo-ad-lab-router-ks.cfg")) [String] $wslSourceISOPath = (New-OZOWSLPathFromWindowsPath -WindowsPath $this.sourceISOPath) [String] $wslTargetISOPath = (New-OZOWSLPathFromWindowsPath -WindowsPath $this.customISOOutputPath) [String] $wslTargetISOLabel = $this.customISOLabel [String] $wslScriptPath = (New-OZOWSLPathFromWindowsPath -WindowsPath (Join-Path -Path $this.customISOLinuxDir -ChildPath "ozo-create-router-iso.sh")) # Determine if this ISO is valid If ($this.Validates -eq $true) { # ISO is valid; use WSL Debian to call the AlmaLinux ISO customization script $wslResult = (& wsl --distribution "Debian" --user root KICKSTART_PATH="$wslKickstartPath" SOURCE_ISO_PATH="$wslSourceISOPath" TARGET_ISO_PATH="$wslTargetISOPath" TARGET_ISO_LABEL="$wslTargetISOLabel" $wslScriptPath) If ($wslResult -eq "TRUE") { # Move the ISO If ($this.MoveISO() -eq $true) { # Moved ISO; report success $this.Messages.Add("Success") } } Else { $this.Messages.Add(("Error creating " + $this.customISOOutputPath)) } } } # Validate Windows ISO method Hidden [Boolean] ValidateWinISO() { # Control variable [Boolean] $Return = $true # Determine if oscdimg.exe is not present If ((Test-Path -Path $this.oscdimgExePath) -eq $false) { # Did not find oscdimg.exe; report $this.Logger.Write("Missing oscdimg.exe. Please see https://onezeroone.dev/active-directory-lab-part-ii-customization-prerequisites/ for more information.","Error") $Return = $false } # Determine if the custom ISO already exists in Downloads directory If ((Test-Path -Path $this.customISOMovePath) -eq $true) { # Custom ISO exists in the Downloads directory $this.Messages.Add(("Found " + $this.customISOMovePath + "; skipping.")) $Return = $false } # Determine if the custom ISO already exists in the OZO AD Lab directory If ((Test-Path -Path $this.customISOOutputPath) -eq $true) { # Custom ISO exists in the OZO AD Lab directory $this.Messages.Add(("Found " + $this.customISOOutputPath + "; skipping.")) $Return = $false } # Determine if the source ISO does not exist If ((Test-Path -Path $this.sourceISOPath) -eq $false) { # Source ISO does not exist $this.Messages.Add(("Missing " + $this.sourceISOPath + "; skipping.")) $Return = $false } # Determine that the build directory exists If ((Test-Path -Path $this.customISOBuildDir) -eq $false) { $this.Messages.Add("Build directory does not exist; skipping.") $Return = $false } # Switch for build-specific criteria Switch($this.Build) { "Client" { # Determine if the Windows 11 Enterprise Languages & Optional Features ISO does not exist If ((Test-Path -Path $this.sourceLAoFISOPath) -eq $false) { # LAoF ISO does not exist $this.Messages.Add(("Missing " + $this.sourceLAoFISOPath + "; skipping.")) $Return = $false } break } "DC" { break } "Server" { break } default { break } } # Return return $Return } # Process Windows ISO method Hidden [Void] ProcessWinISO() { # Mount the source ISO If ($this.MountISO($this.sourceISOPath) -eq $true) { # Source ISO mount; copy the contents to the build directory If ($this.CopyISO((Join-Path -Path $this.mountDrive -ChildPath "*"),$this.customISOBuildDir) -eq $true) { # Contents copied; dismount the ISO If ($this.DismountISO($this.sourceISOPath) -eq $true) { # ISO dismounted; move the install WIM If ($this.MoveWIM((Join-Path -Path $this.customISOBuildDir -ChildPath "sources\install.wim"),$this.customWIMDir) -eq $true) { # Moved WIM; export the desired Index back to the build directory If ($this.ExportImage((Join-Path -Path $this.customWIMDir -ChildPath "install.wim"),$this.sourceIndex,(Join-Path -Path $this.customISOBuildDir -ChildPath "sources\install.wim")) -eq $true) { # Exported the Index back to install.wim; switch for build-specific steps Switch($this.Build) { "Client" { # Mount the WIM for customization If ($this.MountWIM((Join-Path -Path $this.customISOBuildDir -ChildPath "sources\install.wim"),1,$this.customISOMountDir) -eq $true) { # Mounted the WIM for customization; mount the LAoF ISO If ($this.MountISO($this.SourceLAoFISOPath) -eq $true) { # LAoF ISO mounted; Install RSAT If ($this.InstallRSAT() -eq $true) { # RSAT installation succeeded If ($this.DismountWIM($this.customISOMountDir,$true)) { If ($this.DismountISO($this.SourceLAoFISOPath) -eq $true) { # Create the Windows ISO If ($this.CreateWinISO() -eq $true) { # Move the ISO If ($this.MoveISO() -eq $true) { # Moved ISO; report success $this.Messages.Add("Success") } } } } } Else { # RSAT installation failed; dismount the WIM (abandoning changes) and the ISO $this.DismountWIM($this.customISOMountDir,$false) $this.DismountISO($this.SourceLAoFISOPath) } } } break } "DC" { # Create the Windows ISO If ($this.CreateWinISO() -eq $true) { # Move the ISO If ($this.MoveISO() -eq $true) { # Moved ISO; report success $this.Messages.Add("Success") } } break } "Server" { # Create the Windows ISO If ($this.CreateWinISO() -eq $true) { # Move the ISO If ($this.MoveISO() -eq $true) { # Moved ISO; report success $this.Messages.Add("Success") } } break } default { $this.Messages.Add(("Unhandled build: " + $this.Build)) break } } } } } } Else { # Contents not copied; dismount the ISO $this.DismountISO($this.sourceISOPath) } } } # Copy ISO method Hidden [Boolean] CopyISO($SourcePath,$TargetPath) { # Control variable [Boolean] $Return = $true # Try to Try { Copy-Item -Force -Recurse -Path $SourcePath -Destination $TargetPath -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add("Failed to copy source ISO; skipping.") $Return = $false } # Return return $Return } # Move WIM method Hidden [Boolean] MoveWIM($SourcePath,$TargetPath) { # Control variable [Boolean] $Return = $true # Try to move the WIM Try { Move-Item -Force -Path $SourcePath -Destination $TargetPath -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add(("Failed to move " + $SourcePath + " to " + $TargetPath + "; skipping.")) $Return = $false } # Return return $Return } # Export Image method Hidden [Boolean] ExportImage($ImagePath,$ImageIndex,$ExportedWIMPath) { # Control variable [Boolean] $Return = $true # Try to export the index Try { Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $ImageIndex -DestinationImagePath $ExportedWIMPath -ErrorAction Stop # Success; } Catch { # Failure $this.Messages.Add(("Failed to export Index " + $ImageIndex.ToString() + " from " + $ImagePath + "; skipping.")) $Return = $false } # Return return $Return } # Install RSAT method Hidden [Boolean] InstallRSAT() { # Control variable [Boolean] $Return = $true # Try to install RSAT Try { [String] $rsatSource = (Join-Path -Path $this.mountDrive -ChildPath "LanguagesAndOptionalFeatures") Get-WindowsCapability -Online -Name "RSAT*" -Source $rsatSource -ErrorAction Stop | Add-WindowsCapability -Source $rsatSource -Path $this.customISOMountDir -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add("Failed to install RSAT; skipping.") $Return = $false } # Return return $Return } # Mount WIM method Hidden [Boolean] MountWIM($ImagePath,$ImageIndex,$MountDir) { # Control variable [Boolean] $Return = $true # Try to mount the WIM Try { Mount-WindowsImage -ImagePath $ImagePath -Index $ImageIndex -Path $MountDir -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add(("Failed to mount " + $ImagePath + "; skipping.")) $Return = $false } # Return return $Return } # Dismount WIM method Hidden [Boolean] DismountWIM($MountDir,$Save) { # Control variable [Boolean] $Return = $true # Try to dismount the WIM Try { # Determine if the WIM will be saved If ($Save -eq $true) { # The WIM will be saved Dismount-WindowsImage -Path $MountDir -Save -ErrorAction Stop } Else { # The WIM will not be saved Dismount-WindowsImage -Path $MountDir -Discard -ErrorAction Stop } } Catch { # Failure $this.Messages.Add(("Failed to dismount " + $MountDir + "; skipping.")) $Return = $false } # Return return $Return } # Mount ISO method Hidden [Boolean] MountISO($ISOPath) { # Control variable [Boolean] $Return = $true # Try to mount the ISO Try { Mount-DiskImage -ImagePath $ISOPath -ErrorAction Stop # Success; get the drive letter $this.mountDrive = ((Get-DiskImage -ImagePath $ISOPath -ErrorAction Stop | Get-Volume -ErrorAction Stop).DriveLetter + ":") } Catch { # Failure $this.Messages.Add(("Failed to mount " + $ISOPath + "; skipping.")) $Return = $false } # Return return $Return } # Dismount ISO method Hidden [Boolean] DismountISO($ISOPath) { # Control variable [Boolean] $Return = $true # Try to dismount the ISO Try { Dismount-DiskImage -ImagePath $ISOPath -ErrorAction Stop # Success; } Catch { # Failure $this.Messages.Add(("Failed to dismount " + $ISOPath + "; skipping.")) $Return = $false } # Return return $Return } # Create Windows ISO method Hidden [Boolean] CreateWinISO() { # Control variable [Boolean] $Return = $true # Local variables [String] $labl = $this.customISOLabel [String] $esbp = $this.efisysBinPath [String] $cibd = $this.customISOBuildDir [String] $ciop = $this.customISOOutputPath # Try to create the ISO Try { Start-Process -Wait -NoNewWindow -FilePath $this.oscdimgExePath -ArgumentList "-u2 -udfver102 -t -l$labl -b$esbp $cibd $ciop" -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add(("Failed to create the custom ISO; skipping. Error message is: " + $_)) $Return = $false } # Return return $Return } # Move ISO method Hidden [Boolean] MoveISO() { # Control variable [Boolean] $Return = $true # Try to move the ISO Try { Move-Item -Path $this.customISOOutputPath -Destination $this.customISOMovePath -ErrorAction Stop Unblock-File -Path $this.customISOMovePath -ErrorAction Stop # Success } Catch { # Failure $this.Messages.Add("Failed to move the custom ISO to the Downloads directory; skipping.") $Return = $false } # Return return $Return } } # FUNCTIONS Function New-OZOWSLPathFromWindowsPath { param( [Parameter(Mandatory=$true,HelpMessage="The Windows path to convert")][String]$WindowsPath ) # Local variables return ($WindowsPath.Replace("\","/")).Replace($Env:SystemDrive,("/mnt/" + ($Env:SystemDrive.Split(":"))[0].ToLower())) } # Create a Main object [OZOADLCIMain]::new($OZOADLabDir) | Out-Null # SIG # Begin signature block # MIIfcgYJKoZIhvcNAQcCoIIfYzCCH18CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCeLmFc5O6nZ2Rd # dh2bXxuA1AfZreMLD6gz5lhPXe+0VaCCDPgwggZyMIIEWqADAgECAghkM1HTxzif # 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+RiBRqmsj29fjGCEdAwghHMAgEBMIGM # MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv # bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu # aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ # YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG # 9w0BCQQxIgQgiWskyV8QWhlTc4fGdWL2wO4r77v+JFlrcqXGUqBlSmgwDQYJKoZI # hvcNAQEBBQAEggGAedWVS6WjiY+Xbtm43n7yPu9MqXuB6xNVZjIrJ+smhO4okxgU # FgrUyO45obg7vxo1HCBiWGE2QOaq7UaPzlfoqj+dq8ZJ7OLlhAZkkTrQiaRMxGq2 # m6hXw35bLhYWwt1l/6hEqWG1XNbTIVx4Ex3GVbp61uyNXpg/YRQ7cv4pDNVX9uqq # DhR2Fq5nO9MBNZZDyKNTMb+FBcURanJe3oprd3oJT32cPJ1+qkyAXb1zw2k8H/m1 # wcd1YDcfDdwKCdIhZKlZZHOZ2JbhCqRK/uhNBu1ciNA6REKaf/F8kgoHJ+AW7Q3t # DB6VXU2+iYjJRxG+wq6sUgPy8kEA7yWoBAfN7JSLIvbE06Du/raFKL3zWF7a+L7E # t09cj27yAFG6O1qSjQbBXSz8LpWwA9+LPkztPBZ1ufEgILh95fNd0PeGMNt9vp9v # GpO5IenMiiS+5EZzZlQOP+eEAP2BAL2R6uGtWuF99TYZ3UDK4qLcZ/Vz/vhdLvHn # /Vn2c6dxtdJNwUHuoYIPFjCCDxIGCisGAQQBgjcDAwExgg8CMIIO/gYJKoZIhvcN # AQcCoIIO7zCCDusCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm # MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCCg24TxeTkCH7Co # hFtDYApnZZSkEVmcdXBueVH8prBemAIIQaSn2VIf9f0YDzIwMjUwMzAzMDQzNzA2 # 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 # HZf4peSrHdL4RjGCAlgwggJUAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI # DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt # BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa # WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAzMDMwNDM3MDZaMCgGCSqG # SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ # BDEiBCCCd/uqQaq2mcgFE/v8m0TmJNbeSe5jli47wUKqltIWSDCByQYLKoZIhvcN # AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29 # iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV # BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t # IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9 # xzAKBggqhkjOPQQDAgRHMEUCICAufNIowOikPPAYp1HO1TdTGvgpxlYonw/M9fIq # ASO/AiEA24qlxlclUj9CC9viGEaFkH24TpqB0NrLDSg2D0U261M= # SIG # End signature block |