<# .SYNOPSIS Helper script for installing/uninstalling Microsoft Defender for Downlevel Servers. .DESCRIPTION On install scenario: It first removes MMA workspace when RemoveMMA guid is provided. Next uninstalls SCEP if present and OS version is Server2012R2 Next installs two hotfixes required by the MSI (if they are not installed) Next installs the Microsoft Defender for Downlevel Servers MSI (i.e. md4ws.msi) Finally, it runs the onboarding script, if provided using the parameter OnboardingScript. Please use the script for Group Policy as it is non-interactive; the local onboarding script will fail. On uninstall scenario: It will run the offboarding script, if provided. Uninstalls the MSI unless IsTamperProtected is on. Removes Defender Powershell module, if loaded inside current Powershell session. .INPUTS md4ws.msi .OUTPUTS none .EXAMPLE .\Install.ps1 .EXAMPLE .\Install.ps1 -UI -NoMSILog -NoEtl .EXAMPLE .\Install.ps1 -Uninstall .EXAMPLE .\Install.ps1 -Uninstall -NoEtl #> param( [Parameter(ParameterSetName = 'install')] ## MMA Workspace Id to be removed [guid] $RemoveMMA, [Parameter(ParameterSetName = 'install')] ## Path to onboarding script (required by WD-ATP) [string] $OnboardingScript, [Parameter(ParameterSetName = 'install')] ## Installs devmode msi instead of the realeased one [switch] $DevMode, [Parameter(ParameterSetName = 'uninstall', Mandatory)] ## Uninstalls Microsoft Defender for Downlevel Servers. Offboarding has to be run manually prior to uninstall. [switch] $Uninstall, [Parameter(ParameterSetName = 'uninstall')] [Parameter(ParameterSetName = 'install')] ## Offboarding script to run prior to uninstalling/reinstalling MSI [string] $OffboardingScript, [Parameter(ParameterSetName = 'install')] [Parameter(ParameterSetName = 'uninstall')] ## Enables UI in MSI [switch] $UI, [Parameter(ParameterSetName = 'install')] ## Put WinDefend in passive mode. [switch] $Passive, [Parameter(ParameterSetName = 'install')] [Parameter(ParameterSetName = 'uninstall')] ## Disable MSI Logging [switch] $NoMSILog, [Parameter(ParameterSetName = 'install')] ## Used to pass extra arguments to Invoke-WebRequest calls used by this script (like WebSession, Proxy, ProxyCredential) [hashtable] $ExtraWebRequestOptions = @{}, [Parameter(ParameterSetName = 'install')] [Parameter(ParameterSetName = 'uninstall')] ## Disable ETL logging [switch] $NoEtl) function Get-CommandLine { <# .synopsis Returns the equivalent command line used to invoke a script .DESCRIPTION Returns the equivalent command line used to invoke a script .EXAMPLE Get-CommandLine $PSCmdLet.MyInvocation #> [CmdletBinding()] [OutputType([string])] ## Usually $PSCmdLet.MyInvocation. param([Parameter(ValueFromPipeline = $true, Position = 0)] [System.Management.Automation.InvocationInfo] $info) process { function EscapeString { param([string] $s) if ($null -ne $s -and ' ' -in $s -and $s[0] -ne '"') { "`"{0}'" -f $s } else { $s } } [string] $commandLine = '' if ($null -ne $info) { $commandLine = EscapeString $info.MyCommand.Name foreach ($boundparam in $info.BoundParameters.GetEnumerator()) { $val = '' foreach ($k in ($boundparam.Value)) { $val += if ($val.Length) { ',' } else { ':' } $val += if ($k -is [switch]) { if ($k.ToBool()) { '$true' } else { '$false' } } else { EscapeString $k } } $commandLine += " -{0}{1}" -f $boundparam.Key, $val } foreach ($k in $info.UnboundArguments.GetEnumerator()) { $commandLine += " {0}" -f (EscapeString $k) } } return $commandLine } } function Set-RegistryKey { [CmdletBinding()] param([Parameter(Mandatory)][string] $LiteralPath, [Parameter(Mandatory)][string] $Name, [Parameter(Mandatory)][object] $Value) function Set-ContainerPath { [CmdletBinding()] param([Parameter(Mandatory)][string] $LiteralPath) if (!(Test-Path -LiteralPath:$LiteralPath -PathType:Container)) { $parent = Split-Path -Path:$LiteralPath -Parent Set-ContainerPath -LiteralPath:$parent $leaf = Split-Path -Path:$LiteralPath -Leaf $null = New-Item -Path:$parent -Name:$leaf -ItemType:Directory } } Set-ContainerPath -LiteralPath:$LiteralPath Set-ItemProperty -LiteralPath:$LiteralPath -Name:$Name -Value:$Value Trace-Message "$LiteralPath[$Name]=$Value" -SkipFrames:3 } function Remove-RegistryKey { [CmdletBinding()] param ( [Parameter(Mandatory)][string] $LiteralPath ) if (Test-Path -LiteralPath:$LiteralPath) { Remove-Item -LiteralPath:$LiteralPath -Recurse -Force -ErrorAction SilentlyContinue -ErrorVariable err if ($err.count -gt 0){ Trace-Message "Remove-Item $LiteralPath message: $err" -SkipFrames:3 } else { Trace-Message "Removed registry key: $LiteralPath" -SkipFrames:3 } } } [System.IO.StreamWriter] $Script:InstallLog = $null Set-Variable -Name:'InstallPS1HKLM' -Value:'HKLM:\SOFTWARE\Microsoft\Microsoft Defender for Endpoint Install' -Option:Constant -Scope:Script function Get-TraceMessage { [OutputType([string])] param( [Parameter(Mandatory, Position = 0)] [string] $Message, [Parameter(Position = 1)][uint16] $SkipFrames = 2, [datetime] $Date = (Get-Date)) function Get-Time { param([datetime] $Date = (Get-Date)) return $Date.ToString('yy/MM/ddTHH:mm:ss.fff') } [System.Management.Automation.CallStackFrame[]] $stackFrames = Get-PSCallStack for ($k = $SkipFrames; $k -lt $stackFrames.Count; $k++) { $currentPS = $stackFrames[$k] if ($null -ne $currentPS.ScriptName -or $currentPS.FunctionName -eq "") { [int] $lineNumber = $currentPS.ScriptLineNumber if ($null -ne $currentPS.ScriptName) { $scriptFullName = $currentPS.ScriptName } else { if ($null -eq (Get-Variable VMPosition -ErrorAction:Ignore)) { $scriptFullName = '' } else { $lineNumber += $VMPosition.Line $scriptFullName = $VMPosition.File } } $scriptName = $scriptFullName.Substring(1 + $scriptFullName.LastIndexOf('\')) return "[{0}:{1:00} {2} {3}:{4,-3}] {5}" -f $env:COMPUTERNAME, [System.Threading.Thread]::CurrentThread.ManagedThreadId, (Get-Time $date), $scriptName, $lineNumber, $message } } throw "Cannot figure out the right caller for $SkipFrames, $stackFrames" } function Get-CurrentBootSession { [CmdletBinding()] [OutputType([string])] param() return (Get-CimInstance -ClassName:Win32_OperatingSystem).LastBootUpTime.ToString('yy/MM/ddTHH:mm:ss.fff') } function Exit-Install { [CmdletBinding()] param ([Parameter(Mandatory, Position = 0)] [string] $Message, [Parameter(Mandatory)] [uint32] $ExitCode) $fullMessage = Get-TraceMessage -Message:$Message if ($Script:ERR_PENDING_REBOOT -eq $ExitCode) { ## Subsequent runs of this scripts will be able to detect if a reboot happend since it was requested Set-RegistryKey -LiteralPath:$Script:InstallPS1HKLM -Name:'PendingReboot' -Value:$(Get-CurrentBootSession) } if ($null -ne $Script:InstallLog) { $Script:InstallLog.WriteLine($fullMessage) $exitMessage = Get-TraceMessage -Message ("Script will exit with code {0}(0x{0:x})" -f $ExitCode) -SkipFrames:1 $Script:InstallLog.WriteLine($exitMessage) $Script:InstallLog.Close() $Script:InstallLog = $null } Write-Error $fullMessage -ErrorAction:Continue exit $ExitCode } function Trace-Message { [CmdletBinding()] param ([Parameter(Mandatory, Position = 0)] [string] $Message, [Parameter(Position = 1)][uint16] $SkipFrames = 2, [datetime] $Date = (Get-Date)) $fullMessage = Get-TraceMessage -Message:$Message -SkipFrames:$SkipFrames -Date:$Date if ($null -ne $Script:InstallLog) { $Script:InstallLog.WriteLine($fullMessage) } Write-Host $fullMessage } function Trace-Warning { [CmdletBinding()] param ([Parameter(Mandatory)] [string] $Message) $fullMessage = Get-TraceMessage "WARNING: $message" ## not using Write-Warning is intentional. if ($null -ne $Script:InstallLog) { $Script:InstallLog.WriteLine($fullMessage) } Write-Host $fullMessage } function Use-Object { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyString()] [AllowEmptyCollection()] [AllowNull()] [Object]$InputObject, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Object[]]$ArgumentList ) try { & $ScriptBlock @ArgumentList } catch { throw } finally { if ($null -ne $InputObject -and $InputObject -is [System.IDisposable]) { $InputObject.Dispose() } } } function New-TempFile { #New-TemporaryFile is not available on PowerShell 4.0. [CmdletBinding()] [OutputType('System.IO.FileInfo')] param() $path = [System.Io.Path]::GetTempPath() + [guid]::NewGuid().Guid + '.tmp' return New-Object -TypeName 'System.IO.FileInfo' -ArgumentList:$path } function Get-Digest { <# .SYNOPSIS Returns an unique digest dependent on $sa array #> param ([string[]] $sa) $sb = New-Object -TypeName:'System.Collections.Generic.List[byte]' $sha256 = [System.Security.Cryptography.SHA256]::Create() foreach ($element in $sa) { $null = $sb.AddRange($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($element))) } $hash = $sha256.ComputeHash($sb.ToArray()) $sha256.Dispose() $rez = New-Object -TypeName:'System.Text.StringBuilder' foreach ($hb in $hash) { $null = $rez.Append($hb.ToString('X2')) } return $rez.ToString() } function Get-ScriptVersion { [CmdLetBinding()] param([string] $LiteralPath) ## DO NOT EDIT THIS BLOCK - BEGIN $version = @{ Major = '1' Minor = '20231204' Patch = '0' Metadata = 'E8E12DEA' } ## DO NOT EDIT THIS BLOCK - END [bool] $seen = $false $scriptLines = @(Get-Content -Path:$LiteralPath | ForEach-Object { $line = $_ if (-not $seen) { $seen = $line -ieq "#Copyright (C) Microsoft Corporation. All rights reserved." if ($line -match "^\s*Metadata\s*=\s*'([0-9A-F]{8})'") { # skip it } else { $line } } }) $digest = (Get-Digest -sa:$scriptLines).Substring(0, 8) if ($digest -ne $version.Metadata) { $version.Patch = '1' } return "$($version.Major).$($version.Minor).$($version.Patch)+$digest" } function Measure-Process { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateScript( { Test-Path -LiteralPath:$_ -PathType:Leaf })] [string] $FilePath, [AllowEmptyString()] [AllowEmptyCollection()] [string[]] $ArgumentList, [switch] $PassThru, [ValidateScript( { Test-Path -LiteralPath:$_ -PathType:Container })] [string] $WorkingDirectory = (Get-Location).Path, [uint16] $SkipFrames = 3) Trace-Message "Running $FilePath $ArgumentList in $WorkingDirectory ..." -SkipFrames:$SkipFrames $startParams = @{ FilePath = $FilePath WorkingDirectory = $WorkingDirectory Wait = $true NoNewWindow = $true PassThru = $true RedirectStandardOutput = New-TempFile RedirectStandardError = New-TempFile } if ($ArgumentList) { $startParams.ArgumentList = $ArgumentList } $info = @{ ExitCode = 1 } try { Use-Object ($proc = Start-Process @startParams) { param ($ArgumentList, $SkipFrames) [TimeSpan] $runningTime = ($proc.ExitTime - $proc.StartTime).Ticks $exitCode = $info.exitCode = $proc.ExitCode $info.ExitTime = $proc.ExitTime Get-Content -Path $startParams.RedirectStandardOutput | ForEach-Object { Trace-Message "[StandardOutput]: $_" -Date:$info.ExitTime -SkipFrames:$(1 + $SkipFrames) } Get-Content -Path $startParams.RedirectStandardError | ForEach-Object { Trace-Message "[StandardError]: $_" -Date:$info.ExitTime -SkipFrames:$(1 + $SkipFrames) } $commandLine = $(Split-Path -Path:$FilePath -Leaf) if ($ArgumentList) { $commandLine += " $ArgumentList" } $message = if (0 -eq $exitCode) { "Command `"$commandLine`" run for $runningTime" } else { "Command `"$commandLine`" failed with error $exitCode after $runningTime" } Trace-Message $message -SkipFrames:$SkipFrames if (-not $PassThru -and 0 -ne $exitCode) { exit $exitCode } } -ArgumentList:$ArgumentList, (2 + $SkipFrames) } catch { throw } finally { Remove-Item -LiteralPath:$startParams.RedirectStandardError.FullName -Force -ErrorAction:SilentlyContinue Remove-Item -LiteralPath:$startParams.RedirectStandardOutput.FullName -Force -ErrorAction:SilentlyContinue } if ($PassThru) { return $info.ExitCode } } function Test-CurrentUserIsInRole { [CmdLetBinding()] param([string[]] $SIDArray) foreach ($sidString in $SIDArray) { $sid = New-Object System.Security.Principal.SecurityIdentifier($sidString) $role = $sid.Translate([Security.Principal.NTAccount]).Value if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole($role)) { return $true } } return $false } function Get-GuidHelper { [CmdletBinding()] param ( # Parameter help description [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory)] [string] $Value, [Parameter(Mandatory)] [string] $LiteralPath, [Parameter(Mandatory)] [string] $Pattern ) ## guids are regenerated every time we change .wx{i,s} files ## @note: SilentlyContinue just in case $Path does not exist. $result = @(Get-ChildItem -LiteralPath:$LiteralPath -ErrorAction:SilentlyContinue | Where-Object { $_.GetValue($Name) -match $Value -and $_.PSChildName -match $Pattern } | Select-Object -ExpandProperty:PSChildName) if ($result.Count -eq 1) { return $result[0] } return $null } function Get-UninstallGuid { [CmdletBinding()] param ( # Parameter help description [Parameter(Mandatory)] [string] $DisplayName ) $extraParams = @{ Name = 'DisplayName' Value = $DisplayName LiteralPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' Pattern = '^{[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}}$' } return Get-GuidHelper @extraParams } function Get-CodeSQUID { [CmdletBinding()] param ( [string] $ProductName ) if (-not (Get-PSDrive -Name:'HKCR' -ErrorAction:SilentlyContinue)) { $null = New-PSDrive -Name:'HKCR' -PSProvider:Registry -Root:HKEY_CLASSES_ROOT -Scope:Script Trace-Message "'HKCR' PSDrive created(script scoped)" } ## msi!MsiGetProductInfoW $extraParams = @{ Name = 'ProductName' Value = $ProductName LiteralPath = 'HKCR:\Installer\Products' Pattern = '^[0-9a-f]{32}$' } return Get-GuidHelper @extraParams } function Test-IsAdministrator { Test-CurrentUserIsInRole 'S-1-5-32-544' } function Get-FileVersion { [OutputType([System.Version])] [CmdletBinding()] param([Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $File) $versionInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo($File) New-Object System.Version $($versionInfo.FileMajorPart), $($versionInfo.FileMinorPart), $($versionInfo.FileBuildPart), $($versionInfo.FilePrivatePart) } function Get-OSVersion { [OutputType([System.Version])] [CmdletBinding()] param () # [environment]::OSVersion.Version on PowerShell ISE has issues on 2012R2 (see https://devblogs.microsoft.com/scripting/use-powershell-to-find-operating-system-version/) # Get-CIMInstance provides a string where we don't get the revision. return Get-FileVersion -File:"$env:SystemRoot\system32\ntoskrnl.exe" } function Invoke-Member { [CmdletBinding()] param ( [Object] $ComObject, [Parameter(Mandatory)] [string] $Method, [System.Object[]] $ArgumentList) if ($ComObject) { return $ComObject.GetType().InvokeMember($Method, [System.Reflection.BindingFlags]::InvokeMethod, $null, $ComObject, $ArgumentList) } } function Invoke-GetProperty { [CmdletBinding()] param ( [Object] $ComObject, [Parameter(Mandatory)] [string] $Property, [Parameter(Mandatory)] [int] $Colummn) if ($ComObject) { return $ComObject.GetType().InvokeMember($Property, [System.Reflection.BindingFlags]::GetProperty, $null, $ComObject, $Colummn) } } function ReleaseComObject { [CmdletBinding()] param ([Object] $ComObject) if ($ComObject) { $null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObject) } } function Get-MsiFilesInfo { [CmdletBinding()] param ([Parameter(Mandatory)] [string] $MsiPath) function Get-MsiFileTableHelper { param ([Parameter(Mandatory)] [Object] $Database) try { ## @see https://docs.microsoft.com/en-us/windows/win32/msi/file-table $view = Invoke-Member $Database 'OpenView' ("SELECT * FROM File") Invoke-Member $view 'Execute' $rez = @{} while ($null -ne ($record = Invoke-Member $view 'Fetch')) { $file = Invoke-GetProperty $record 'StringData' 1 $FileName = Invoke-GetProperty $record 'StringData' 3 $versionString = $(Invoke-GetProperty $record 'StringData' 5) $version = if ($versionString) { [version]$versionString } else { $null } $rez.$file = [ordered] @{ Component = Invoke-GetProperty $record 'StringData' 2 FileName = $FileName FileSize = [convert]::ToInt64($(Invoke-GetProperty $record 'StringData' 4)) Version = $version Language = Invoke-GetProperty $record 'StringData' 6 Attributes = [convert]::ToInt16($(Invoke-GetProperty $record 'StringData' 7)) Sequence = [convert]::ToInt16($(Invoke-GetProperty $record 'StringData' 8)) } ReleaseComObject $record } return $rez } catch { throw } finally { Invoke-Member $view 'Close' ReleaseComObject $view } } try { $installer = New-Object -ComObject:WindowsInstaller.Installer ## @see https://docs.microsoft.com/en-us/windows/win32/msi/database-object $database = Invoke-Member $installer 'OpenDatabase' ($MsiPath, 0) return Get-MsiFileTableHelper -Database:$database } catch { throw } finally { ReleaseComObject $database ReleaseComObject $installer } } function Test-ExternalScripts { [CmdletBinding()] param () if ($OnboardingScript.Length) { if (-not (Test-Path -LiteralPath:$OnboardingScript -PathType:Leaf)) { Exit-Install -Message:"$OnboardingScript does not exist" -ExitCode:$ERR_ONBOARDING_NOT_FOUND } ## validate it is an "onboarding" script. $on = Get-Content -LiteralPath:$OnboardingScript | Where-Object { $_ -match 'add\s+"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Advanced Threat Protection"\s+\/v\s+OnboardingInfo' } if ($on.Length -eq 0) { Exit-Install -Message:"Not an onboarding script: $OnboardingScript" -ExitCode:$ERR_INVALID_PARAMETER } if (-not (Test-IsAdministrator)) { Exit-Install -Message:'Onboarding scripts need to be invoked from an elevated process' -ExitCode:$ERR_INSUFFICIENT_PRIVILEGES } $pause = Get-Content -LiteralPath:$OnboardingScript | Where-Object { $_ -imatch '^pause$' } if ($pause.Length -ne 0) { ## Please read: https://github.com/microsoft/mdefordownlevelserver#project Exit-Install -Message:"Please use the onboarding script for Group Policy as it is non-interactive, $OnboardingScript might wait for user input" -ExitCode:$ERR_INVALID_SCRIPT_TYPE } } if ($OffboardingScript.Length) { if (-not (Test-Path -LiteralPath:$OffboardingScript -PathType:Leaf)) { Exit-Install -Message:"$OffboardingScript does not exist" -ExitCode:$ERR_OFFBOARDING_NOT_FOUND } $off = Get-Content -LiteralPath:$OffboardingScript | Where-Object { $_ -match 'add\s+"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Advanced Threat Protection"\s+\/v\s+696C1FA1-4030-4FA4-8713-FAF9B2EA7C0A' } if ($off.Length -eq 0) { Exit-Install -Message:"Not an offboarding script: $OffboardingScript" -ExitCode:$ERR_INVALID_PARAMETER } if (-not (Test-IsAdministrator)) { Exit-Install -Message:'Offboarding scripts need to be invoked from an elevated process' -ExitCode:$ERR_INSUFFICIENT_PRIVILEGES } $pause = Get-Content -LiteralPath:$OffboardingScript | Where-Object { $_ -imatch '^pause$' } if ($pause.Length -ne 0) { ## Please read: https://github.com/microsoft/mdefordownlevelserver#project Exit-Install -Message:"Please use the offboarding script for Group Policy as it is non-interactive, $OffboardingScript might wait for user input" -ExitCode:$ERR_INVALID_SCRIPT_TYPE } } } function Get-RegistryKey { [CmdLetBinding()] param([Parameter(Mandatory)][ValidateNotNullOrEmpty()][string] $LiteralPath, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string] $Name) ## @note: Get-ItemPropertyValue ... -ErrorAction:SilentlyContinue is complaining about errors. $k = Get-ItemProperty -LiteralPath:$LiteralPath -Name:$Name -ErrorAction:SilentlyContinue if ($k) { return $k.$Name } return $null } function Invoke-MpCmdRun { [CmdLetBinding()] param( [AllowEmptyString()] [AllowEmptyCollection()] [string[]] $ArgumentList, [uint16] $SkipFrames = 4 ) $startParams = @{ FilePath = Join-Path -Path:$(Get-RegistryKey -LiteralPath:'HKLM:\SOFTWARE\Microsoft\Windows Defender' -Name:'InstallLocation') 'MpCmdRun.exe' SkipFrames = $SkipFrames } if ($ArgumentList) { $startParams.ArgumentList = $ArgumentList } Measure-Process @startParams } function Start-TraceSession { [CmdLetBinding()] param() $guid = [guid]::NewGuid().Guid $wdprov = Join-Path -Path:$env:TEMP "$guid.temp" $tempFile = Join-Path -Path:$env:TEMP "$guid.etl" $etlLog = "$PSScriptRoot\$logBase.etl" $wppTracingLevel = 'WppTracingLevel' $reportingPath = 'HKLM:\Software\Microsoft\Windows Defender\Reporting' $etlParams = @{ ArgumentList = @($PSScriptRoot, $logBase, $wdprov, $tempFile, $etlLog, $wppTracingLevel, $reportingPath) } if (-not (Test-IsAdministrator)) { # non-administrator should be able to install. $etlParams.Credential = Get-Credential -UserName:Administrator -Message:"Administrator credential are required for starting an ETW session:" $etlParams.ComputerName = 'localhost' $etlParams.EnableNetworkAccess = $true } if (Test-Path -LiteralPath:$etlLog -PathType:leaf) { if (Test-Path -LiteralPath:"$PSScriptRoot\$logBase.prev.etl") { Remove-Item -LiteralPath:"$PSScriptRoot\$logBase.prev.etl" -ErrorAction:Stop } Rename-Item -LiteralPath:$etlLog -NewName:"$logBase.prev.etl" -ErrorAction:Stop } $scmWppTracingKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Tracing\SCM\Regular' $scmWppTracingValue = 'TracingDisabled' $scmTracingDisabled = Get-RegistryKey -LiteralPath:$scmWppTracingKey -Name:$scmWppTracingValue if (1 -eq $scmTracingDisabled) { ## certain SCM issues could be investigated only if ebcca1c2-ab46-4a1d-8c2a-906c2ff25f39 is enabled. ## Unfortunatelly SCM does not register ebcca1c2-ab46-4a1d-8c2a-906c2ff25f39 when HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Tracing\SCM\Regular[TracingDisabled] is (DWORD)1, ## therefore it cannot be enabled / disabled without a restart. Trace-Warning "Service Control Manager tracing is disabled (see $scmWppTracingKey[$scmWppTracingValue])" } Invoke-Command @etlparams -ScriptBlock: { param($ScriptRoot, $logBase, $wdprov, $tempFile, $etlLog, $wppTracingLevel, $reportingPath); ## enable providers $providers = @( @{Guid = 'ebcca1c2-ab46-4a1d-8c2a-906c2ff25f39'; Flags = 0x0FFFFFFF; Level = 0xff; Name = "SCM" }, @{Guid = 'B0CA1D82-539D-4FB0-944B-1620C6E86231'; Flags = 0xffffffff; Level = 0xff; Name = 'EventLog' }, @{Guid = 'A676B545-4CFB-4306-A067-502D9A0F2220'; Flags = 0xfffff; Level = 0x5; Name = 'setup' }, @{Guid = '81abafee-28b9-4df5-bb2d-5b0be87829f5'; Flags = 0xff; Level = 0x1f; Name = 'mpwixca' }, @{Guid = '68edb168-7705-494b-a746-9297abdc91d3'; Flags = 0xff; Level = 0x1f; Name = 'mpsigstub' }, @{Guid = '2a94554c-2fbe-46d0-9fa6-60562281b0cb'; Flags = 0xff; Level = 0x1f; Name = 'msmpeng' }, @{Guid = 'db30e9dc-354d-48b5-9dc0-aeaebc5c6b54'; Flags = 0xff; Level = 0x1f; Name = 'mpclient' }, @{Guid = 'ac45fef1-612b-4066-85a7-dd0a5e8a7f30'; Flags = 0xff; Level = 0x1f; Name = 'mpsvc' }, @{Guid = '5638cd78-bc82-608a-5b69-c9c7999b411c'; Flags = 0xff; Level = 0x1f; Name = 'mpengine' }, @{Guid = '449df70e-dba7-42c8-ba01-4d0911a4aecb'; Flags = 0xff; Level = 0x1f; Name = 'mpfilter' }, @{Guid = 'A90E9218-1F47-49F5-AB71-9C6258BD7ECE'; Flags = 0xff; Level = 0x1f; Name = 'mpcmdrun' }, @{Guid = '0c62e881-558c-44e7-be07-56b991b9401a'; Flags = 0xff; Level = 0x1f; Name = 'mprtp' }, @{Guid = 'b702d31c-f586-4fc0-bcf5-f929745199a4'; Flags = 0xff; Level = 0x1f; Name = 'nriservice' }, @{Guid = '4bc60e5e-1e5a-4ec8-b0a3-a9efc31c6667'; Flags = 0xff; Level = 0x1f; Name = 'nridriver' }, @{Guid = 'FFBD47B1-B3A9-4E6E-9A44-64864363DB83'; Flags = 0xff; Level = 0x1f; Name = 'mpdlpcmd' }, @{Guid = '942bda7f-e07d-5a00-96d3-92f5bcb7f377'; Flags = 0xff; Level = 0x1f; Name = 'mpextms' }, @{Guid = 'bc4992b8-a44c-4f70-834b-9d45df9b1824'; Flags = 0xff; Level = 0x1f; Name = 'WdDevFlt' } ) Set-Content -LiteralPath:$wdprov -Value:"# {PROVIDER_GUID}FLAGSLEVEL" -Encoding:ascii $providers | ForEach-Object { # Any line that starts with '#','*',';' is commented out # '-' in front of a provider disables it. # {PROVIDER_GUID}FLAGSLEVEL Add-Content -LiteralPath:$wdprov -Value:("{{{0}}} {1} {2}" -f $_.Guid, $_.Flags, $_.Level) -Encoding:ascii } try { $jobParams = @{ Name = "Setting up $wppTracingLevel" ScriptBlock = { param([string] $reportingPath, [string] $wppTracingLevel) function Set-RegistryKey { [CmdletBinding()] param([Parameter(Mandatory)][string] $LiteralPath, [Parameter(Mandatory)][string] $Name, [Parameter(Mandatory)][object] $Value) function Set-ContainerPath { [CmdletBinding()] param([Parameter(Mandatory)][string] $LiteralPath) if (!(Test-Path -LiteralPath:$LiteralPath -PathType:Container)) { $parent = Split-Path -Path:$LiteralPath -Parent Set-ContainerPath -LiteralPath:$parent $leaf = Split-Path -Path:$LiteralPath -Leaf $null = New-Item -Path:$parent -Name:$leaf -ItemType:Directory } } Set-ContainerPath -LiteralPath:$LiteralPath Set-ItemProperty -LiteralPath:$LiteralPath -Name:$Name -Value:$Value } Set-RegistryKey -LiteralPath:$reportingPath -Name:$wppTracingLevel -Value:0 -ErrorAction:SilentlyContinue } ArgumentList = @($reportingPath, $wppTracingLevel) ScheduledJobOption = New-ScheduledJobOption -RunElevated } try { $scheduledJob = Register-ScheduledJob @jobParams -ErrorAction:Stop $taskParams = @{ TaskName = $scheduledJob.Name Action = New-ScheduledTaskAction -Execute $scheduledJob.PSExecutionPath -Argument:$scheduledJob.PSExecutionArgs Principal = New-ScheduledTaskPrincipal -UserId:'NT AUTHORITY\SYSTEM' -LogonType:ServiceAccount -RunLevel:Highest } $scheduledTask = Register-ScheduledTask @taskParams -ErrorAction:Stop Start-ScheduledTask -InputObject:$scheduledTask -ErrorAction:Stop -AsJob | Wait-Job | Remove-Job -Force -Confirm:$false $SCHED_S_TASK_RUNNING = 0x41301 do { Start-Sleep -Milliseconds:10 $LastTaskResult = (Get-ScheduledTaskInfo -InputObject:$scheduledTask).LastTaskResult } while ($LastTaskResult -eq $SCHED_S_TASK_RUNNING) } catch { Trace-Warning "Error: $_" } finally { if ($scheduledJob) { Unregister-ScheduledJob -InputObject $scheduledJob -Force } if ($scheduledTask) { Unregister-ScheduledTask -InputObject $scheduledTask -Confirm:$false } } $wpp = Get-RegistryKey -LiteralPath:$reportingPath -Name:$wppTracingLevel if ($null -eq $wpp) { Trace-Warning "$reportingPath[$wppTracingLevel] could not be created" } else { Trace-Message "$reportingPath[$wppTracingLevel]=$wpp" } #Set-ItemProperty -LiteralPath:'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Tracing\SCM\Regular' -Name:'TracingDisabled' -Value:0 & logman.exe create trace -n $logBase -pf $wdprov -ets -o $tempFile *>$null if (0 -eq $LASTEXITCODE) { Trace-Message "Tracing session '$logBase' started." } else { Trace-Warning "logman.exe create trace -n $logBase -pf $wdprov -ets -o $tempFile exited with exitcode $LASTEXITCODE" } } catch { throw } finally { Remove-Item -LiteralPath:$wdprov -ErrorAction:Continue } } return $etlParams } $currentDate = Get-Date $Installps1LogName = "InstallPS1-$env:COMPUTERNAME.$($currentDate.ToString('yyMMddTHHmmssfffzzz').Replace(':', '')).log" if (-not $NoMSILog.IsPresent -or -not $NoEtl.IsPresent) { $InstallLogPath = Join-Path $PSScriptRoot -ChildPath:$Installps1LogName try { $Script:InstallLog = New-Object -TypeName:'System.IO.StreamWriter' -ArgumentList:@($InstallLogPath, $true) Trace-Message "$($PSCmdLet.MyInvocation.MyCommand.Name) traces will be saved to $InstallLogPath" } catch { Trace-Warning "Error: $_" } } Trace-Message "Running command: $(Get-CommandLine $PSCmdLet.MyInvocation)" @( @{ Name = 'ERR_INTERNAL'; Value = 1 } ## Not used. @{ Name = 'ERR_INSUFFICIENT_PRIVILEGES'; Value = 3 } ## Are you running as Administrator? @{ Name = 'ERR_NO_INTERNET_CONNECTIVITY'; Value = 4 } ## Are you behind a proxy? Is network on? @{ Name = 'ERR_CONFLICTING_APPS'; Value = 5 } ## Not used. @{ Name = 'ERR_INVALID_PARAMETER'; Value = 6 } ## Are you providing the right parameters to this script? Did you missmatch **On**boardingScript with an **Off**boarding script or vice-versa? @{ Name = 'ERR_UNSUPPORTED_DISTRO'; Value = 10 } ## Is this a server SKU? Is this '2012 R2' or '2016' Server? @{ Name = 'ERR_UNSUPPORTED_VERSION'; Value = 11 } ## Uninstall using the regular Administator account (Using System was fixed in Feb 2023) @{ Name = 'ERR_PENDING_REBOOT'; Value = 12 } ## A dependent component requested a reboot. @{ Name = 'ERR_INSUFFICIENT_REQUIREMENTS'; Value = 13 } ## A requirement was not satisfied, cannot continue. @{ Name = 'ERR_UNEXPECTED_STATE'; Value = 14 } ## Cannot handle the task in the current state of the product. Manual intervention is required. @{ Name = 'ERR_CORRUPTED_FILE'; Value = 15 } ## All executable files (and this script) should be signed. Was one of the files (md4ws.msi) truncated? @{ Name = 'ERR_MSI_NOT_FOUND'; Value = 16 } ## Is the MSI in the same directory like this file? @{ Name = 'ERR_ALREADY_UNINSTALLED'; Value = 17 } ## Not used. @{ Name = 'ERR_DIRECTORY_NOT_WRITABLE'; Value = 18 } ## Current directory should be writeable (to write the installation/uninstallation logs) @{ Name = 'ERR_MDE_NOT_INSTALLED'; Value = 20 } ## Cannot uninstall something that is not installed. @{ Name = 'ERR_INSTALLATION_FAILED'; Value = 21 } ## Not used. @{ Name = 'ERR_UNINSTALLATION_FAILED'; Value = 22 } ## Not used. @{ Name = 'ERR_FAILED_DEPENDENCY'; Value = 23 } ## Not used @{ Name = 'ERR_ONBOARDING_NOT_FOUND'; Value = 30 } ## Check passed onboarding script path. Does it point to an existing file? @{ Name = 'ERR_ONBOARDING_FAILED'; Value = 31 } ## Onboarding script failed. @{ Name = 'ERR_OFFBOARDING_NOT_FOUND'; Value = 32 } ## Check passed offboarding script path. Does it point to an existing file? @{ Name = 'ERR_OFFBOARDING_FAILED'; Value = 33 } ## Offboarding script failed. @{ Name = 'ERR_NOT_ONBOARDED'; Value = 34 } ## Cannot offboard if not onboarded @{ Name = 'ERR_NOT_OFFBOARDED'; Value = 35 } ## Cannot onboard if already onboarded. @{ Name = 'ERR_MSI_USED_BY_OTHER_PROCESS'; Value = 36 } ## md4ws.msi is opened by a process (orca.exe?!), preventing a successful installation. @{ Name = 'ERR_INVALID_SCRIPT_TYPE'; Value = 37 } ## Onboarding/Offboading scripts shouldn't require any user interaction. @{ Name = 'ERR_TAMPER_PROTECTED'; Value = 38 } ## Uninstallation cannot continue, since the product is still tamper protected. @{ Name = 'ERR_MDE_GROUP_POLICY_DISABLED'; Value = 39 } ## HKLM:\Software\Policies\Microsoft\Windows Defender[DisableAntiSpyware] is set to 1. ) | ForEach-Object { Set-Variable -Name:$_.Name -Value:$_.Value -Option:Constant -Scope:Script } if (-not [System.Environment]::Is64BitOperatingSystem) { Exit-Install "Only 64 bit OSes (Server 2012 R2 or Server 2016) are currently supported by this script" -ExitCode:$ERR_UNSUPPORTED_DISTRO } elseif (-not [System.Environment]::Is64BitProcess) { Trace-Warning "Current process IS NOT 64bit. Did you start a 'Windows Powershell (x86)'?!" $nativePowershell = "$env:SystemRoot\sysnative\windowspowershell\v1.0\powershell.exe" if (-not (Test-Path -LiteralPath:$nativePowershell -PathType:Leaf)) { Exit-Install "Cannot figure out 64 bit powershell location. Please run this script from a 64bit powershell." -ExitCode:$ERR_UNEXPECTED_STATE } [System.Collections.ArrayList] $argumentList = New-Object -TypeName:'System.Collections.ArrayList' $argumentList.AddRange(@('-NoProfile', '-NonInteractive', '-File', $MyInvocation.MyCommand.Path)) if ($MyInvocation.BoundParameters.Count -gt 0) { function Get-EscapeString { param([string] $s) if ($null -ne $s -and ' ' -in $s -and $s[0] -ne '"') { "`"{0}'" -f $s } else { $s } } foreach ($boundparam in $MyInvocation.BoundParameters.GetEnumerator()) { if ($boundparam.Value -is [switch]) { if ($boundparam.Value.ToBool()) { $null = $argumentList.Add($("-{0}" -f $boundparam.Key)) } } else { $val = '' foreach ($k in ($boundparam.Value)) { $val += if ($val.Length) { ',' } else { ':' } $val += Get-EscapeString $k } $null = $argumentList.Add($("-{0}{1}" -f $boundparam.Key, $val)) } } foreach ($k in $MyInvocation.UnboundArguments.GetEnumerator()) { $null = $argumentList.Add($("{0}" -f (Get-EscapeString $k))) } } $psArgumentList = $argumentList.ToArray() Trace-Message "Running $nativePowershell $psArgumentList" & $nativePowershell $psArgumentList if (-not $?) { Trace-Warning "$nativePowershell $psArgumentList exited with exitcode $LASTEXITCODE" } exit $LASTEXITCODE } Test-ExternalScripts if ('Tls12' -notin [Net.ServicePointManager]::SecurityProtocol) { ## Server 2016/2012R2 might not have this one enabled and all Invoke-WebRequest might fail. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 Trace-Message "[Net.ServicePointManager]::SecurityProtocol updated to '$([Net.ServicePointManager]::SecurityProtocol)'" } if ($null -eq $ExtraWebRequestOptions) { $ExtraWebRequestOptions = @{} } else { ## validate ExtraWebRequestOptions hash [bool] $validExtraWebRequestOptions = $true foreach ($useOption in 'Uri', 'OutFile', 'ErrorAction', 'UseBasicParsing') { if ($ExtraWebRequestOptions.ContainsKey($useOption)) { Trace-Warning "Please remove $useOption from ExtraWebRequestOption hash and try again." $validExtraWebRequestOptions = $false } } if (-not $validExtraWebRequestOptions) { Exit-Install -Message:"Invalid parameter ExtraWebRequestOption (see the above warnings)" -ExitCode:$ERR_INVALID_PARAMETER } } $osVersion = Get-OSVersion ## make sure we capture logs by default. [bool] $etl = -not $NoEtl.IsPresent [bool] $log = -not $NoMSILog.IsPresent [string] $msi = if ($DevMode.IsPresent -or ((Test-Path -Path:"$PSScriptRoot\md4ws-devmode.msi") -and -not (Test-Path -Path:"$PSScriptRoot\md4ws.msi"))) { ## This is used internally (never released to the public) by product team to test private builds. Join-Path -Path:$PSScriptRoot "md4ws-devmode.msi" } else { Join-Path -Path:$PSScriptRoot "md4ws.msi" } $action = if ($Uninstall.IsPresent) { 'uninstall' } else { 'install' } $logBase = "$action-$env:COMPUTERNAME" if ($etl -or $log) { ## make sure $PSSCriptRoot is writable. $tempFile = Join-Path -Path:$PSScriptRoot "$([guid]::NewGuid().Guid).tmp" Set-Content -LiteralPath:$tempFile -Value:'' -ErrorAction:SilentlyContinue if (-not (Test-Path -LiteralPath:$tempFile -PathType:Leaf)) { Exit-Install "Cannot create $tempFile. Is $PSScriptRoot writable?" -ExitCode:$ERR_DIRECTORY_NOT_WRITABLE } else { Remove-Item -LiteralPath:$tempFile -ErrorAction:SilentlyContinue $tempFile = $null } } $etlParams = @{} try { $tempMsiLog = Join-Path -Path:$env:TEMP "$([guid]::NewGuid().Guid).log" [System.IO.FileStream] $msiStream = $null if ($action -eq 'install') { ## $msi should be checked as early as possible, see ICM#413339981 if (-not (Test-Path -LiteralPath:$msi -PathType:leaf)) { Exit-Install "$msi does not exist. Please download latest $(Split-Path -Path:$msi -Leaf) into $PSScriptRoot and try again." -ExitCode:$ERR_MSI_NOT_FOUND } else { try { $msiStream = [System.IO.File]::OpenRead($msi) Trace-Message ("Handle {0} opened over {1}" -f $msiStream.SafeFileHandle.DangerousGetHandle(), $msi) } catch { ## Orca (https://docs.microsoft.com/en-us/windows/win32/msi/orca-exe) likes to keep a opened handle to $msi ## and if installation happens during this time Get-AuthenticodeSignature will get an 'Unknown' status. ## Same with msiexec.exe, so better check for this scenario here. Exit-Install "Cannot open $msi for read: $_.Exception" -ExitCode:$ERR_MSI_USED_BY_OTHER_PROCESS } $status = (Get-AuthenticodeSignature -FilePath:$msi).Status if ($status -ne 'Valid') { Exit-Install "Unexpected authenticode signature status($status) for $msi" -ExitCode:$ERR_CORRUPTED_FILE } Trace-Message "$($(Get-FileHash -LiteralPath:$msi).Hash) $msi" } } if ($null -ne $RemoveMMA) { $mma = New-Object -ComObject 'AgentConfigManager.MgmtSvcCfg' $workspaces = @($mma.GetCloudWorkspaces() | Select-Object -ExpandProperty:workspaceId) if ($RemoveMMA -in $workspaces) { Trace-Message "Removing cloud workspace $($RemoveMMA.Guid)..." $mma.RemoveCloudWorkspace($RemoveMMA) $workspaces = @($mma.GetCloudWorkspaces() | Select-Object -ExpandProperty:workspaceId) if ($workspaces.Count -gt 0) { $mma.ReloadConfiguration() } else { Stop-Service HealthService } Trace-Message "Workspace $($RemoveMMA.Guid) removed." } else { Exit-Install "Invalid workspace id $($RemoveMMA.Guid)" -ExitCode:$ERR_INVALID_PARAMETER } } $msiLog = "$PSScriptRoot\$logBase.log" if ($log -and (Test-Path -LiteralPath:$msiLog -PathType:Leaf)) { if (Test-Path -LiteralPath:"$PSScriptRoot\$logBase.prev.log") { Remove-Item -LiteralPath:"$PSScriptRoot\$logBase.prev.log" -ErrorAction:Stop } Rename-Item -LiteralPath:$msiLog -NewName:"$PSScriptRoot\$logBase.prev.log" } ## The new name is 'Microsoft Defender for Endpoint' - to avoid confusions on Server 2016. $displayName = 'Microsoft Defender for (Windows Server|Endpoint)' $uninstallGUID = Get-UninstallGuid -DisplayName:$displayName ## Next 3 traces are here because they are helpful for investigations. $buildLabEx = Get-RegistryKey -LiteralPath:'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name:'BuildLabEx' Trace-Message "BuildLabEx: $buildLabEx" $editionID = Get-RegistryKey -LiteralPath:'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name:'EditionID' Trace-Message "EditionID: $editionID" $lastBootTime = Get-CurrentBootSession Trace-Message "LastBootUpTime: $lastBootTime" Trace-Message "CurrentTime : $($currentDate.ToString('yy/MM/ddTHH:mm:ss.fffzzz'))" $scriptPath = $MyInvocation.MyCommand.Path Trace-Message "$($MyInvocation.MyCommand.Name) version: $(Get-ScriptVersion -LiteralPath:$scriptPath)" $pendingReboot = Get-RegistryKey -LiteralPath:$Script:InstallPS1HKLM -Name:'PendingReboot' if ($pendingReboot -eq $lastBootTime) { Trace-Warning "Previous run of $($PSCmdLet.MyInvocation.MyCommand.Name) requested a reboot" Exit-Install -Message:"Please restart this computer to continue $($PSCmdLet.MyInvocation.MyCommand.Name) actions" -ExitCode:$ERR_PENDING_REBOOT } elseif ($null -ne $pendingReboot) { Remove-ItemProperty -LiteralPath:$Script:InstallPS1HKLM -Name:'PendingReboot' -ErrorAction:SilentlyContinue } if ($action -eq 'install') { if ($osVersion.Major -eq 6 -and $osVersion.Minor -eq 3) { $windefend = Get-Service -Name:'WinDefend' -ErrorAction:SilentlyContinue $wdnissvc = Get-Service -Name:'WdNisSvc' -ErrorAction:SilentlyContinue $wdfilter = Get-Service -Name:'WdFilter' -ErrorAction:SilentlyContinue if ($windefend -and -not $wdnissvc -and -not $wdfilter) { ## workaround for ICM#278342470 (or VSO#37292177). Fixed on MOCAMP version 4.18.2111.150 or newer. if ($windefend.Status -eq 'Running') { Exit-Install "Please reboot this computer to remove 'WinDefend' Service" -ExitCode:$ERR_PENDING_REBOOT } elseif ($windefend.Status -eq 'Stopped') { $winDefendServicePath = 'HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend' if (Test-Path -LiteralPath:$winDefendServicePath) { $imagePath = Get-RegistryKey -LiteralPath:$winDefendServicePath -Name:'ImagePath' Trace-Message "WinDefend service is Stopped. ImagePath is $imagePath. Trying to remove $winDefendServicePath" Remove-Item -LiteralPath:$winDefendServicePath -Force -Recurse -ErrorAction:SilentlyContinue if (Test-Path -LiteralPath:$winDefendServicePath) { Exit-Install "Cannot remove $winDefendServicePath" -ExitCode:$ERR_UNEXPECTED_STATE } } else { Trace-Warning "WinDefend service is stopped but $winDefendServicePath is gone. This usually happens when running this script more than once without restarting the machine." } Exit-Install "Please restart this machine to complete 'WinDefend' service removal" -ExitCode:$ERR_PENDING_REBOOT } else { Exit-Install -Message:"Unexpected WinDefend service status: $($windefend.Status)" -ExitCode:$ERR_UNEXPECTED_STATE } } ## SCEP is different on Server 2016. $path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Security Client" if (Test-Path -LiteralPath:$path) { $displayName = (Get-ItemProperty -LiteralPath:$path -Name:'DisplayName').DisplayName # See camp\src\amcore\Antimalware\Source\AppLayer\Components\Distribution\Common\CmdLineParser.h $exitCode = Measure-Process -FilePath:"$env:ProgramFiles\Microsoft Security Client\Setup.exe" -ArgumentList:@('/u', '/s') -PassThru if (0 -eq $exitCode) { Trace-Message "Uninstalling '$displayName' successful." } else { Trace-Warning "Uninstalling '$displayName' exitcode: $exitCode." } } # Server2012R2 needs two KBs to be installed ... function Install-KB { [CmdletBinding()] param([string] $Uri, [string]$KB, [scriptblock] $scriptBlock) $present = & $scriptBlock if ($present) { return } $PreviousProgressPreference = $ProgressPreference $outFile = Join-Path -Path:$env:TEMP $((New-Object System.Uri $Uri).Segments[-1]) try { $ProgressPreference = 'SilentlyContinue' if (Get-HotFix -Id:$KB -ErrorAction:SilentlyContinue) { Trace-Message "$KB already installed." return } Trace-Message "Downloading $KB to $outFile" Invoke-WebRequest -Uri:$Uri -OutFile:$outFile -ErrorAction:Stop @ExtraWebRequestOptions Trace-Message "Installing $KB" $link = "https://support.microsoft.com/kb/{0}" -f $($KB.Substring(2)) $exitCode = Measure-Process -FilePath:$((Get-Command 'wusa.exe').Path) -ArgumentList:@($outFile, '/quiet', '/norestart') -PassThru if (0 -eq $exitCode) { Trace-Message "$KB installed." } elseif (0x80240017 -eq $exitCode) { #0x80240017 = WU_E_NOT_APPLICABLE = Operation was not performed because there are no applicable updates. Exit-Install -Message:"$KB not applicable, please follow the instructions from $link" -ExitCode:$ERR_INSUFFICIENT_REQUIREMENTS } elseif (0xbc2 -eq $exitCode) { #0xbc2=0n3010,ERROR_SUCCESS_REBOOT_REQUIRED The requested operation is successful. Changes will not be effective until the system is rebooted Exit-Install -Message "$KB required a reboot" -ExitCode:$ERR_PENDING_REBOOT } else { Exit-Install -Message:"$KB installation failed with exitcode: $exitCode. Please follow the instructions from $link" -ExitCode:$exitCode } } catch { ## not ok to ignore, MSI will simply fail with generic error 1603. throw } finally { $ProgressPreference = $PreviousProgressPreference if (Test-Path -LiteralPath:$outFile -PathType:Leaf) { Trace-Message "Removing $outFile" Remove-Item -LiteralPath:$outFile -Force -ErrorAction:SilentlyContinue } } } <## The minimum number of KBs to be applied (in this order) to a RTM Server 2012R2 image to have a successful install: KB2919442 prerequisite for KB2919355, https://www.microsoft.com/en-us/download/details.aspx?id=42153 KB2919355 prerequisite for KB3068708, KB2999226 and KB3080149, https://www.microsoft.com/en-us/download/details.aspx?id=42334 KB2999226 needed by WinDefend service, https://www.microsoft.com/en-us/download/details.aspx?id=49063 KB3080149 telemetry dependency, https://www.microsoft.com/en-us/download/details.aspx?id=48637 KB2959977 prerequisite for KB3045999, https://www.microsoft.com/en-us/download/details.aspx?id=42529 KB3068708 prerequisite for KB3045999, https://www.microsoft.com/en-us/download/details.aspx?id=47362 KB3045999 workaround for VSO#35611997, https://www.microsoft.com/en-us/download/details.aspx?id=46547 To see the list of installed hotfixes run: 'Get-HotFix | Select-Object -ExpandProperty:HotFixID' #> ## ucrt dependency (needed by WinDefend service) - see https://www.microsoft.com/en-us/download/confirmation.aspx?id=49063 Install-KB -Uri:'https://download.microsoft.com/download/D/1/3/D13E3150-3BB2-4B22-9D8A-47EE2D609FFF/Windows8.1-KB2999226-x64.msu' -KB:KB2999226 -ScriptBlock: { $ucrtbaseDll = "$env:SystemRoot\system32\ucrtbase.dll" if (Test-Path -LiteralPath:$ucrtbaseDll -PathType:Leaf) { $verInfo = Get-FileVersion -File:$ucrtbaseDll Trace-Message "$ucrtBaseDll version is $verInfo" return $true } Trace-Warning "$ucrtbaseDll not present, trying to install KB2999226" return $false } ## telemetry dependency (needed by Sense service) - see https://www.microsoft.com/en-us/download/details.aspx?id=48637 Install-KB -Uri:'https://download.microsoft.com/download/A/3/E/A3E82C15-7762-4104-B969-6A486C49DB8D/Windows8.1-KB3080149-x64.msu' -KB:KB3080149 -ScriptBlock: { $tdhDll = "$env:SystemRoot\system32\Tdh.dll" if (Test-Path -LiteralPath:$tdhDll -PathType:Leaf) { $fileVersion = Get-FileVersion -File:$tdhDll $minFileVersion = New-Object -TypeName:System.Version -ArgumentList:6, 3, 9600, 17958 if ($fileVersion -ge $minFileVersion) { Trace-Message "$tdhDll version is $fileVersion" return $true } Trace-Warning "$tdhDll version is $fileVersion (minimum version is $minFileVersion), trying to install KB3080149" return $false } Trace-Warning "$tdhDll not present, trying to install KB3080149" return $false } ## needed by Sense - see VSO#35611997 Install-KB -Uri:'https://download.microsoft.com/download/3/9/E/39EAFBBF-A801-4D79-B2B1-DAC4673AFB09/Windows8.1-KB3045999-x64.msu' -KB:KB3045999 -ScriptBlock: { $osVersion = Get-OSVersion $minNtVersion = New-Object -TypeName:System.Version -ArgumentList:6, 3, 9600, 17736 if ($osVersion -ge $minNtVersion) { Trace-Message "OsVersion is $osVersion" return $true } Trace-Warning "Current ntoskrnl.exe version is $osVersion (minimum required is $minNtVersion), trying to install KB3045999" return $false } $disableAntiSpywareGP = Get-RegistryKey -LiteralPath:'HKLM:\Software\Policies\Microsoft\Windows Defender' -Name:'DisableAntiSpyware' if ($disableAntiSpywareGP) { Exit-Install "Remove(or change it to 0) HKLM:\Software\Policies\Microsoft\Windows Defender[DisableAntiSpyware] and try installing again." -ExitCode:$ERR_MDE_GROUP_POLICY_DISABLED } } elseif ($osVersion.Major -eq 10 -and $osVersion.Minor -eq 0 -and $osVersion.Build -lt 18362) { $defenderFeature = Get-WindowsOptionalFeature -Online -FeatureName:'Windows-Defender' -ErrorAction:Stop if ($defenderFeature.State -ne 'Enabled') { #see ICM#394684348 Remove-RegistryKey -LiteralPath:'HKLM:\System\CurrentControlSet\Services\EventLog\Microsoft-Windows-Windows Defender/Operational' $defenderFeature = $defenderFeature | Enable-WindowsOptionalFeature -Online -NoRestart } if ($defenderFeature.RestartNeeded) { Exit-Install "Restart is required by 'Windows-Defender'" -ExitCode:$ERR_PENDING_REBOOT } if ($null -eq $uninstallGUID) { $codeSQUID = Get-CodeSQUID -ProductName:$displayName if ($null -ne $codeSQUID) { ## Workaround for ICM#320556857 ## Previous version of this product was not properly uninstalled triggering an upgrade scenario ## that fails because MSSecFlt.inf is missing. Trace-Warning "Previously installed msi was not properly uninstalled(code:$codeSQUID)" foreach ($subdir in 'Products', 'Features') { $item = "HKCR:\Installer\$subdir\$codeSQUID" if (Test-Path -LiteralPath:$item -PathType:Container) { Rename-Item -LiteralPath:$item -NewName:"$codeSQUID~" -ErrorAction:Stop Trace-Warning "$item renamed to $codeSQUID~" } else { Trace-Warning "$item not present" } } } } $windefendStatus = (Get-Service -Name:'WinDefend' -ErrorAction:SilentlyContinue).Status Trace-Message "'WindDefend' service status is '$windefendStatus'" $imageName = (Get-RegistryKey -LiteralPath:'HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend' -Name:ImagePath) -replace '"', '' if (0 -eq $imageName.Length) { Trace-Warning "'WinDefend' image path is null or empty. Still the '$($defenderFeature.FeatureName)' feature state is $($defenderFeature.State)" ## Workaround for ICM#423646508. In this scenario, "Restart is required by 'Windows-Defender'" error is ignored and this script is executed again without a restart. ## Windows-Defender optional feature is seen as 'Enabled' (but no services/files are present on the computer just yet) and previous versions of this script started ## to report issues unrelated with the issue at hand. Exit-Install "Restart is required by 'Windows-Defender'" -ExitCode:$ERR_PENDING_REBOOT } $currentVersion = Get-FileVersion -File:$imageName if ($currentVersion -lt '4.18.23050.5') { Trace-Warning "Current platform version is $currentVersion, a platform update is needed." } if ($windefendStatus -ne 'Running') { $disableAntiSpywareGP = Get-RegistryKey -LiteralPath:'HKLM:\Software\Policies\Microsoft\Windows Defender' -Name:'DisableAntiSpyware' if ($disableAntiSpywareGP) { ## ICM#383475289 - WinDefend disabled via GP Exit-Install "Remove(or change it to 0) HKLM:\Software\Policies\Microsoft\Windows Defender[DisableAntiSpyware] and try installing again." -ExitCode:$ERR_MDE_GROUP_POLICY_DISABLED } if ($currentVersion -lt '4.18.2102.4' -and $windefendStatus -eq 'Stopped') { ## ICM#391597247. Possible scenarios: ## - WinDefend was disabled via 'mpcmdrun.exe disableservice' sometimes between 2017 to 2021. ## - or the platform was reset to inbox version and after that disabled. $isDisabled = (Get-Service -Name:'WinDefend' -ErrorAction:SilentlyContinue).StartType -eq 'Disabled' if ($isDisabled) { #define SERVICE_AUTO_START 0x00000002 #define SERVICE_DISABLED 0x00000004 Set-ItemProperty -LiteralPath:'HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend' -Name:'Start' -Value:2 -ErrorAction:SilentlyContinue $isDisabled = (Get-Service -Name:'WinDefend' -ErrorAction:SilentlyContinue).StartType -eq 'Disabled' if ($isDisabled) { Exit-Install "Cannot enable 'WinDefend' service" -ExitCode:$ERR_UNEXPECTED_STATE } Trace-Message "'WinDefend' service has been enabled." } } ## try to start it using 'mpcmdrun wdenable' (best effort) $disableAntiSpyware = Get-RegistryKey -LiteralPath:'HKLM:\Software\Microsoft\Windows Defender' -Name:'DisableAntiSpyware' if ($null -ne $disableAntiSpyware -and 0 -ne $disableAntiSpyware) { Trace-Warning "DisableAntiSpyware is set to $disableAntiSpyware (should be zero)" } Invoke-MpCmdRun -ArgumentList:@('WDEnable') $windefendStatus = (Get-Service -Name:'WinDefend' -ErrorAction:SilentlyContinue).Status } # Server 2016 - Windows Defender is shipped with OS, need to check if inbox version is updatable and latest. # Expectations are that 'Windows Defender Features' are installed and up-to-date if ($windefendStatus -eq 'Running') { if ($currentVersion -lt '4.10.14393.2515') { Exit-Install 'Windows Defender platform update requirement not met. Please apply the latest cumulative update (LCU) for Windows first. Minimum required is https://support.microsoft.com/en-us/help/4457127' -ExitCode:$ERR_INSUFFICIENT_REQUIREMENTS } $previousProgressPreference = $Global:ProgressPreference $deleteUpdatePlatform = $false try { $Global:ProgressPreference = 'SilentlyContinue' $msiVersion = (Get-MsiFilesInfo -MsiPath:$msi).'MPCLIENT.DLL'.Version $updatePlatformBaseName = if ($DevMode.IsPresent) { 'UpdatePlatformD.exe' } else { 'UpdatePlatform.exe' } if ($currentVersion -lt $msiVersion) { Trace-Message "Current platform version is $currentVersion, msiVersion is $msiVersion" $updatePlatform = Join-Path -Path:$PSScriptRoot $updatePlatformBaseName if (-not (Test-Path -LiteralPath:$updatePlatform -PathType:Leaf) -and -not $DevMode.IsPresent) { ## Download $updatePlatformBaseName from $uri *only if* the UpdatePlatform is not present. $uri = 'https://go.microsoft.com/fwlink/?linkid=870379&arch=x64' Trace-Message "$updatePlatformBaseName not present under $PSScriptRoot" try { $latestVersion = ([xml]((Invoke-WebRequest -UseBasicParsing -Uri:"$uri&action=info" @ExtraWebRequestOptions).Content)).versions.platform } catch { Trace-Warning "Error: $_" Exit-Install "Cannot download the latest $updatePlatformBaseName. Please download it from $uri under $PSScriptRoot\$updatePlatformBaseName" -ExitCode:$ERR_NO_INTERNET_CONNECTIVITY } if ($latestVersion -lt $msiVersion) { Trace-Warning "Changing msiVersion from $msiVersion to $latestVersion" $msiVersion = $latestVersion } if ($latestVersion -gt $currentVersion) { Trace-Message "Downloading latest $updatePlatformBaseName (version $latestVersion) from $uri" $deleteUpdatePlatform = $true $updatePlatform = Join-Path -Path:$env:TEMP $updatePlatformBaseName Invoke-WebRequest -UseBasicParsing -Uri:$uri -OutFile:$updatePlatform @ExtraWebRequestOptions } else { Trace-Message "Running platform is up-to-date" } } if (Test-Path -LiteralPath:$updatePlatform -PathType:Leaf) { $updatePlatformVersion = Get-FileVersion -File:$updatePlatform if ($updatePlatformVersion -lt $msiVersion) { Exit-Install "Minimum required version is $msiVersion. $updatePlatform version is $updatePlatformVersion" -ExitCode:$ERR_INSUFFICIENT_REQUIREMENTS } $status = (Get-AuthenticodeSignature -FilePath:$updatePlatform).Status if ($status -ne 'Valid') { Exit-Install "Unexpected authenticode signature status($status) for $updatePlatform" -ExitCode:$ERR_CORRUPTED_FILE } ## make sure the right file was downloaded (or present in this directory) $fileInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($updatePlatform) if ($updatePlatformBaseName -ne $fileInfo.InternalName) { Exit-Install "Unexpected file: $updatePlatform, InternalName='$($fileInfo.InternalName)' (expecting '$updatePlatformBaseName')" -ExitCode:$ERR_CORRUPTED_FILE } if ('Microsoft Malware Protection' -ne $fileInfo.ProductName) { Exit-Install "Unexpected file: $updatePlatform, ProductName='$($fileInfo.ProductName)' (expecting 'Microsoft Malware Protection')" -ExitCode:$ERR_CORRUPTED_FILE } Trace-Message ("Running $updatePlatformBaseName (version {0})" -f (Get-FileVersion -File:$updatePlatform)) Measure-Process -FilePath:$updatePlatform $imageName = (Get-ItemPropertyValue -LiteralPath:'HKLM:\SYSTEM\CurrentControlSet\Services\WinDefend' -Name:ImagePath) -replace '"', '' $currentVersion = Get-FileVersion -File:$imageName if ($currentVersion -lt $latestVersion) { Exit-Install "Current version is $currentVersion, expected to be at least $latestVersion" -ExitCode:$ERR_INSUFFICIENT_REQUIREMENTS } } Trace-Message "Current platform version is $currentVersion" } } catch { throw } finally { $Global:ProgressPreference = $previousProgressPreference if ($deleteUpdatePlatform) { Remove-Item -LiteralPath:$updatePlatform -ErrorAction:SilentlyContinue if (Test-Path -LiteralPath:$updatePlatform -PathType:Leaf) { Trace-Warning "Could not delete $updatePlatform" } else { Trace-Message "$updatePlatform deleted" } } } } else { Exit-Install "'WinDefend' service is not running." -ExitCode:$ERR_UNEXPECTED_STATE } $krnlACL = Get-Acl -Path:"$env:SystemRoot\System32\ntoskrnl.exe" if ($krnlACL.Owner -ne 'NT SERVICE\TrustedInstaller') { ## See ICM#379926141 - unable to install md4ws.msi "Unsupported OS version" $wrongOwner = $krnlACL.Owner $ti = New-Object -TypeName:System.Security.Principal.NTAccount('NT SERVICE\TrustedInstaller') $krnlACL.SetOwner($ti) Set-Acl -Path:"$env:SystemRoot\System32\ntoskrnl.exe" -AclObject:$krnlACL Trace-Warning "Current owner for $env:SystemRoot\System32\ntoskrnl.exe changed from '$wrongOwner' to '$($krnlACL.Owner)'" } } else { Exit-Install "Unsupported OS version: $osVersion" -ExitCode:$ERR_UNSUPPORTED_DISTRO } } [hashtable] $etlParams = @{} $onboardedSense = Get-RegistryKey -LiteralPath:'HKLM:SYSTEM\CurrentControlSet\Services\Sense' -Name:'Start' if ($OffboardingScript.Length -gt 0 -and ($action -eq 'uninstall' -or $null -ne $uninstallGUID)) { if (2 -ne $onboardedSense) { Exit-Install -Message:"Sense Service is not onboarded, nothing to offboard." -ExitCode:$ERR_NOT_ONBOARDED } Trace-Message "Invoking offboarding script $OffboardingScript" $scriptPath = if ($OffboardingScript.Contains(' ') -and -not $OffboardingScript.StartsWith('"')) { '"{0}"' -f $OffboardingScript } else { $OffboardingScript } ## Sense service is delay starting and for offboarding to work without false positives Sense has to run. $senseService = Get-Service -Name:Sense if ($senseService.Status -ne 'Running') { $senseService = Start-Service -Name:Sense -ErrorAction:SilentlyContinue } if ($senseService) { Trace-Message "Sense service status is '$($senseService.Status)'" } if ($etl) { ## Offboard might fail due to WinDefend changes. $etlParams = Start-TraceSession } $exitCode = Measure-Process -FilePath:$((Get-Command 'cmd.exe').Path) -ArgumentList:@('/c', $scriptPath) -PassThru if (0 -eq $exitCode) { Trace-Message "Offboarding script $OffboardingScript reported success." $onboardedSense = Get-RegistryKey -LiteralPath:'HKLM:SYSTEM\CurrentControlSet\Services\Sense' -Name:'Start' $endWait = (Get-Date) + 30 * [timespan]::TicksPerSecond $traceWarning = $true while (2 -eq $onboardedSense -and (Get-Date) -lt $endWait) { if ($traceWarning) { $traceWarning = $false Trace-Warning "HKLM:SYSTEM\CurrentControlSet\Services\Sense[Start] is still 2. Waiting for it to change..." } Start-Sleep -Milliseconds:100 $onboardedSense = Get-RegistryKey -LiteralPath:'HKLM:SYSTEM\CurrentControlSet\Services\Sense' -Name:'Start' } if (2 -eq $onboardedSense) { Exit-Install "`'HKLM:SYSTEM\CurrentControlSet\Services\Sense[Start]`' is still 2(onboarded) so offboarding failed" -ExitCode:$ERR_OFFBOARDING_FAILED } } else { Exit-Install "Offboarding script returned $exitCode." -ExitCode:$exitCode } } if ($action -eq 'uninstall') { [bool] $isTamperProtected = (Get-MpComputerStatus -ErrorAction:SilentlyContinue).IsTamperProtected if ($isTamperProtected) { # This is already encoded in the product, added here for clarity. Exit-Install "Tamper protection is still enabled. Please disable it (or boot in 'Safe Mode') before uninstalling the product." -ExitCode:$ERR_TAMPER_PROTECTED } & logman.exe query "SenseIRTraceLogger" -ets *>$null if (0 -eq $LASTEXITCODE) { Trace-Warning "SenseIRTraceLogger still present, removing it!" & logman.exe stop -n "SenseIRTraceLogger" -ets *>$null if (0 -ne $LASTEXITCODE) { Trace-Warning "SenseIRTraceLogger could not be removed, exitCode=$LASTEXITCODE" } } try { # MsSense executes various Powershell scripts and these ones start executables that are not tracked anymore by the MsSense.exe or SenseIr.exe # This is mitigated in the latest package but for previously installed packages we have to implement this hack to have a successful uninstall. $procs = Get-Process -ErrorAction:SilentlyContinue foreach ($proc in $procs) { foreach ($m in $proc.Modules.FileName) { if ($m.StartsWith("$env:ProgramFiles\Windows Defender Advanced Threat Protection\") -or $m.StartsWith("$env:ProgramData\Microsoft\Windows Defender Advanced Threat Protection\")) { Trace-Warning "Terminating outstanding process $($proc.Name)(pid:$($proc.Id))" Stop-Process -InputObject:$proc -Force -ErrorAction:Stop break } } } } catch { Trace-Warning "Error: $_" Exit-Install "Offboarding left processes that could not be stopped" -ExitCode:$ERR_OFFBOARDING_FAILED } ## Once in a long while (after an UpdateSenseClient.exe update) MsSecFlt cannot be unloaded anymore. ## a kernel dump is needed to investigate this issue so this block stays here until we are lucky. $msSecFlt = Get-Service -Name:'MsSecFlt' -ErrorAction:SilentlyContinue if ($null -ne $msSecFlt -and $msSecFlt.Status -eq 'Running' -and -not $msSecFlt.CanStop) { Trace-Warning "Service '$($msSecFlt.Name)' cannot be stopped, reboot is required" Exit-Install "Please restart this machine to complete '$($msSecFlt.Name)' service removal" -ExitCode:$ERR_PENDING_REBOOT } # dealing with current powershell session that error out after this script finishes. foreach ($name in 'ConfigDefender', 'Defender') { $defender = Get-Module $name -ErrorAction:SilentlyContinue if ($defender) { Remove-Module $defender Trace-Message 'Defender module unloaded.' break } } if ($osVersion.Major -eq 6 -and $osVersion.Minor -eq 3) { if (Test-CurrentUserIsInRole 'S-1-5-18') { if ($null -ne $uninstallGUID) { $displayVersion = Get-RegistryKey -LiteralPath:"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${uninstallGUID}" -Name:'DisplayVersion' if ($displayVersion -contains '4.18.' -and $displayVersion -lt '4.18.60326') { # See ICM#337407672 - This will be (or has been) fixed with build 4.18.2301.126 such that newer md4ws.msi could be uninstalled from system account. # Older msis have to be uninstalled from a normal Administrator account Exit-Install "Uninstallation of version $displayVersion is not supported from System account. Try uninstalling from a regular Administrator account" -ErrorAction:$ERR_UNSUPPORTED_VERSION } else { Trace-Warning "Running uninstall from System account - UninstallGUID is $uninstallGUID" } } else { Trace-Warning "Running uninstall from System account" } } $needWmiPrvSEMitigation = if ($null -ne $uninstallGUID) { $displayVersion = Get-RegistryKey -LiteralPath:"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${uninstallGUID}" -Name:'DisplayVersion' ## newer versions handle this via custom action inside the MSI $displayVersion -lt '4.18.60321' } else { $true } if ($needWmiPrvSEMitigation) { # dealing with WmiPrvSE.exe keeping ProtectionManagement.dll in use (needed only on Server 2012 R2) Get-Process -Name:'WmiPrvSE' -ErrorAction:SilentlyContinue | ForEach-Object { if ($_.MainModule.FileName -ne "$env:SystemRoot\system32\wbem\wmiprvse.exe") { return } [string] $loadedModule = '' foreach ($m in $_.Modules.FileName) { if ($m.StartsWith("$env:ProgramFiles\Windows Defender\") -or $m.StartsWith("$env:ProgramData\Microsoft\Windows Defender\Platform\")) { $loadedModule = $m break } } if ($loadedModule.Length -gt 0) { Trace-Warning "Terminating $($proc.Name)(pid:$($proc.Id)) because has '$loadedModule' in use" Stop-Process $_.Id -Force -ErrorAction:Stop } } } } } if (2 -eq $onboardedSense) { # all MSI operations (installing, uninstalling, upgrading) should be performed while Sense is offboarded. Exit-Install -Message:"Sense Service is onboarded, offboard before reinstalling(or use -OffboardingScript with this script)" -ExitCode:$ERR_NOT_OFFBOARDED } $argumentList = if ($action -eq 'install') { if ($msi.Contains(' ')) { @('/i', "`"$msi`"") } else { @('/i', $msi) } } else { if ($null -eq $uninstallGUID) { Exit-Install "'$displayName' already uninstalled." -ExitCode:$ERR_MDE_NOT_INSTALLED } @('/x', $uninstallGUID) } if ($log) { $argumentList += '/lvx*+' $argumentList += if ($tempMsiLog.Contains(' ')) { "`"$tempMsiLog`"" } else { $tempMsiLog } } if (-not $UI.IsPresent) { $argumentList += '/quiet' } if ($Passive.IsPresent) { Trace-Message "Will force passive mode." $argumentList += 'FORCEPASSIVEMODE=1' } if ($etl -and 0 -eq $etlParams.Count) { ## start ETW session if not already. $etlParams = Start-TraceSession } $exitCode = Measure-Process -FilePath:$((Get-Command 'msiexec.exe').Path) -ArgumentList:$argumentList -PassThru if (0 -eq $exitCode) { Trace-Message "$action successful." } else { Exit-Install "$action exitcode: $exitCode" -ExitCode:$exitCode } if ($action -eq 'install') { if ($null -ne $codeSQUID) { ## install succeeded, no need to keep around these 2 registry keys. foreach ($subdir in 'Products', 'Features') { $itemPath = "HKCR:\Installer\$subdir\$codeSQUID~" if (Test-Path -LiteralPath:$itemPath -PathType:Container) { try { Remove-Item -LiteralPath:$itemPath -Recurse -ErrorAction:Stop Trace-Message "$itemPath recusively removed" } catch { Trace-Warning "Failed to remove $itemPath" } } } } if ($OnboardingScript.Length) { Trace-Message "Invoking onboarding script $OnboardingScript" $scriptPath = if ($OnboardingScript.Contains(' ') -and -not $OnboardingScript.StartsWith('"')) { '"{0}"' -f $OnboardingScript } else { $OnboardingScript } $argumentList = @('/c', $scriptPath) $exitCode = Measure-Process -FilePath:$((Get-Command 'cmd.exe').Path) -ArgumentList:$argumentList -PassThru if (0 -eq $exitCode) { Trace-Message "Onboarding successful." } else { Trace-Warning "Onboarding script returned $exitCode" } } } } catch { throw } finally { if ($msiStream.CanRead) { Trace-Message ("Closing handle {0}" -f $msiStream.SafeFileHandle.DangerousGetHandle()) $msiStream.Close() } if ($etlParams.ContainsKey('ArgumentList')) { Invoke-Command @etlparams -ScriptBlock: { param($ScriptRoot, $logBase, $wdprov, $tempFile, $etlLog, $wppTracingLevel, $reportingPath) & logman.exe stop -n $logBase -ets *>$null if (0 -eq $LASTEXITCODE) { Trace-Message "Tracing session '$logBase' stopped." } else { Trace-Warning "logman.exe stop -n $logBase -ets returned $LASTEXITCODE" } try { $jobParams = @{ Name = "Cleanup $wppTracingLevel" ScriptBlock = { param($reportingPath, $wppTracingLevel) Remove-ItemProperty -LiteralPath:$reportingPath -Name:$wppTracingLevel -ErrorAction:SilentlyContinue } ArgumentList = @($reportingPath, $wppTracingLevel) ScheduledJobOption = New-ScheduledJobOption -RunElevated } $scheduledJob = Register-ScheduledJob @jobParams -ErrorAction:Stop $taskParams = @{ TaskName = $scheduledJob.Name Action = New-ScheduledTaskAction -Execute $scheduledJob.PSExecutionPath -Argument:$scheduledJob.PSExecutionArgs Principal = New-ScheduledTaskPrincipal -UserId:'NT AUTHORITY\SYSTEM' -LogonType:ServiceAccount -RunLevel:Highest } $scheduledTask = Register-ScheduledTask @taskParams -ErrorAction:Stop Start-ScheduledTask -InputObject:$scheduledTask -ErrorAction:Stop -AsJob | Wait-Job | Remove-Job -Force -Confirm:$false $SCHED_S_TASK_RUNNING = 0x41301 do { Start-Sleep -Milliseconds:10 $LastTaskResult = (Get-ScheduledTaskInfo -InputObject:$scheduledTask).LastTaskResult } while ($LastTaskResult -eq $SCHED_S_TASK_RUNNING) $wpp = Get-RegistryKey -LiteralPath:$reportingPath -Name:$wppTracingLevel if ($null -eq $wpp) { Trace-Message "$reportingPath[$wppTracingLevel] removed" } } catch { Trace-Warning "Error: $_" } finally { if ($scheduledJob) { Unregister-ScheduledJob -InputObject $scheduledJob -Force } if ($scheduledTask) { Unregister-ScheduledTask -InputObject $scheduledTask -Confirm:$false } } Move-Item -LiteralPath:$tempFile -Destination:$etlLog -ErrorAction:Continue Trace-Message "$action.etl file: '$etlLog'." } } else { Trace-Message "No $action.etl file generated." } if ($log -and (Test-Path -LiteralPath:$tempMsiLog -PathType:Leaf)) { Move-Item -LiteralPath:$tempMsiLog -Destination:$msiLog -ErrorAction:Continue Trace-Message "Msi $action.log: '$msiLog'" } else { Trace-Message "No $action.log file generated." } if ($null -ne $Script:InstallLog) { Trace-Message "$($PSCmdLet.MyInvocation.MyCommand.Name) traces: '$InstallLogPath'" $Script:InstallLog.Close() $Script:InstallLog = $null } } #Copyright (C) Microsoft Corporation. All rights reserved. # SIG # Begin signature block # MIImAgYJKoZIhvcNAQcCoIIl8zCCJe8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAFjr2nNTaokDaK # joR4niyDCj+U4v+UCDIh7RRZ5dItvqCCC1MwggTgMIIDyKADAgECAhMzAAAK7CQL # sju2bxocAAAAAArsMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBXaW5kb3dzIFBD # QSAyMDEwMB4XDTIzMTAxOTE5MTgwM1oXDTI0MTAxNjE5MTgwM1owcDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEaMBgGA1UEAxMRTWljcm9zb2Z0 # IFdpbmRvd3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxlYs7SirE # 2DMWmJDHmyPDmkzh+fLl2bNdYJFYVIxEDXmuYo7qVT/TlzRyHZNjfnCpNIN5BGy+ # tL1DHfbYMyeZ64rRBk5ZDyfxpC0PjuOKeo8l1Yp0DYH8o/tovvyg/7t7RBqawaFi # 8mo9wrD5ISkTwSSMv2itkTg00L+gE8awFU17AUmplCQ9mZ91C/9wLp9wH9bIBGm5 # LnsMVzGxaxLbcqzuyi0CUj0ANTuQNZUFNTvLWj/k3W3j7iiNZRDaniVqF2i7UEpU # Twl0A2/ET31/zrvHBzhJKaUtC31IicLI8HqTuUA96FAxGfczxleoZI6jXS2sWSYI # wU6YnckWSSAhAgMBAAGjggFoMIIBZDAfBgNVHSUEGDAWBgorBgEEAYI3CgMGBggr # BgEFBQcDAzAdBgNVHQ4EFgQUK97sk9qa9IVpYVlzmmULjVzY6akwRQYDVR0RBD4w # PKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UEBRMN # MjMwMDI4KzUwMTcwMjAfBgNVHSMEGDAWgBTRT6mKBwjO9CQYmOUA//PWeR03vDBT # BgNVHR8ETDBKMEigRqBEhkJodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNXaW5QQ0FfMjAxMC0wNy0wNi5jcmwwVwYIKwYBBQUHAQEE # SzBJMEcGCCsGAQUFBzAChjtodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl # cnRzL01pY1dpblBDQV8yMDEwLTA3LTA2LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqG # SIb3DQEBCwUAA4IBAQArGdljm580qkATgRqYVsgvfdFUkL/7TpOb8yh1h5vk2SEL # El5Bfz46bs3+ywayV/mXd8Y43M3yku5Dp7dMwRXkze6j4LJLpLQ4CMPN4fvtlPkb # w+fQmXkHjogsb4bcJo/aUKfLy4hGUbw+uqKBLx0RRIEj6Vj2m5W7lB+rdBl8hhtr # v5F4HYoy9lvXQhGGDwSsph+0uaZvCXSP7DOM3wOaYUQSNX6hYF5EHZsPrd334YGd # dTWIPRHrOWqg9FplGJumgZLgdlwY+WNZbXGCZwEQN3P88LTgrH/gmlSD0fHbZDyM # YZ77M6PFlz4eXvC6I7J3VemS8OoU4DzYgxSahDXFMIIGazCCBFOgAwIBAgIKYQxq # GQAAAAAABDANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh # dGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzA2MjA0MDIzWhcNMjUwNzA2MjA1MDIz # WjB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSMwIQYDVQQD # ExpNaWNyb3NvZnQgV2luZG93cyBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEBBQAD # ggEPADCCAQoCggEBAMB5uzqx8A+EuK1kKnUWc9C7B/Y+DZ0U5LGfwciUsDh8H9Az # VfW6I2b1LihIU8cWg7r1Uax+rOAmfw90/FmV3MnGovdScFosHZSrGb+vlX2vZqFv # m2JubUu8LzVs3qRqY1pf+/MNTWHMCn4x62wK0E2XD/1/OEbmisdzaXZVaZZM5Njw # NOu6sR/OKX7ET50TFasTG3JYYlZsioGjZHeYRmUpnYMUpUwIoIPXIx/zX99vLM/a # FtgOcgQo2Gs++BOxfKIXeU9+3DrknXAna7/b/B7HB9jAvguTHijgc23SVOkoTL9r # XZ//XTMSN5UlYTRqQst8nTq7iFnho0JtOlBbSNECAwEAAaOCAeMwggHfMBAGCSsG # AQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTRT6mKBwjO9CQYmOUA//PWeR03vDAZBgkr # BgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw # AwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBN # MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0 # cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoG # CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p # Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBnQYDVR0gBIGVMIGSMIGPBgkrBgEE # AYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9Q # S0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA # YQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI # hvcNAQELBQADggIBAC5Bpoa1Bm/wgIX6O8oX6cn65DnClHDDZJTD2FamkI7+5Jr0 # bfVvjlONWqjzrttGbL5/HVRWGzwdccRRFVR+v+6llUIz/Q2QJCTj+dyWyvy4rL/0 # wjlWuLvtc7MX3X6GUCOLViTKu6YdmocvJ4XnobYKnA0bjPMAYkG6SHSHgv1QyfSH # KcMDqivfGil56BIkmobt0C7TQIH1B18zBlRdQLX3sWL9TUj3bkFHUhy7G8JXOqiZ # VpPUxt4mqGB1hrvsYqbwHQRF3z6nhNFbRCNjJTZ3b65b3CLVFCNqQX/QQqbb7yV7 # BOPSljdiBq/4Gw+Oszmau4n1NQblpFvDjJ43X1PRozf9pE/oGw5rduS4j7DC6v11 # 9yxBt5yj4R4F/peSy39ZA22oTo1OgBfU1XL2VuRIn6MjugagwI7RiE+TIPJwX9hr # cqMgSfx3DF3Fx+ECDzhCEA7bAq6aNx1QgCkepKfZxpolVf1Ayq1kEOgx+RJUeRry # DtjWqx4z/gLnJm1hSY/xJcKLdJnf+ZMakBzu3ZQzDkJQ239Q+J9iguymghZ8Zrzs # mbDBWF2osJphFJHRmS9J5D6Bmdbm78rj/T7u7AmGAwcNGw186/RayZXPhxIKXezF # ApLNBZlyyn3xKhAYOOQxoyi05kzFUqOcasd9wHEJBA1w3gI/h+5WoezrtUyFMYIa # BTCCGgECAQEwgZAweTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEjMCEGA1UEAxMaTWljcm9zb2Z0IFdpbmRvd3MgUENBIDIwMTACEzMAAArsJAuy # O7ZvGhwAAAAACuwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisG # AQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcN # AQkEMSIEIFCGAnf18xiZFgzZEHR7X0NKQYjZ3V0q3LIYEkQJ2VMYMEIGCisGAQQB # gjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEANtZIp7cEkAGCVgU65wL6BFui # 16y194nk6Vh3pL6m9P9qyWrtidW4ZF4xL/z6hoPHfMEMLzI9TDMGWSIUtKCQoU4H # B8a9r6BkenTf6sU5YXWY9ELXrySeya9VwpEBizAIEGdusQg7S+xSnv1f6tU956OC # ijmbDVYSVvNQ7siWEyifefwg9YmjFhMHMTIJMAxkNY4tRcvVx4hT1oARjuQtmQFY # pl60OoUFEvWYQabgwBNF4FaZitQRuJGd4gU7N+1tM3StQ9upqP0ytvdqWbVZGrHT # 4GW5wCY80PIc0pxPr84SHOZ8n1nNcLGiTIoOrXGQBYXBV1RftRlBr78W94H8a6GC # F5QwgheQBgorBgEEAYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgED # MQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIB # AQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDEvC4hd6cwKfs335Ih1pQ6 # 4WjW8/XpfOKz6dV/oCFyWQIGZVbIJ3dNGBMyMDIzMTIwNTEwNDk1Ny4wMDlaMASA # AgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQL # Ex5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jv # c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAdGy # W0AobC7SRQABAAAB0TANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEyMThaFw0yNDAyMDExOTEyMThaMIHLMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNy # b3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBF # U046QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCZTNo0OeGz # 2XFd2gLg5nTlBm8XOpuwJIiXsMU61rwq1ZKDpa443RrSG/pH8Gz6XNnFQKGnCqNC # tmvoKULApwrT/s7/e1X0lNFKmj7U7X4p00S0uQbW6LwSn/zWHaG2c54ZXsGY+BYf # hWDgbFpCTxRzTnRCG62bkWPp6ZHbZPg4Ht1CRCAMhhOGTR8wI4G7wwWZwdMc6UvU # Ulq0ql9AxAfzkYRpi2tRvDHMdmZ3vyXpqhFwvRG8cgCH/TTCjW5q6aNbdqKL3BFD # PzUtuCNsPXL3/E0dR2bDMqa0aNH+iIfhGC4/vcwuteOMCPUIDVSqDCNfIaPDEwYc # i1fd9gu1zVw+HEhDZM7Ea3nxIUrzt+Rfp5ToMMj4QAmJ6Uadm+TPbDbo8kFIK70S # hmW8wn8fJk9ReQQEpTtIN43eRv9QmXy3Ued80osOBE+WkdMvSCFh+qgCsKdzQxQJ # G62cTeoU2eqNhH3oppXmyfVUwbsefQzMPtbinCZd0FUlmlM/dH+4OniqQyaHvrtY # y3wqIafY3zeFITlVAoP9q9vF4W7KHR/uF0mvTpAL5NaTDN1plQS0MdjMkgzZK5gt # wqOe/3rTlqBzxwa7YYp3urP5yWkTzISGnhNWIZOxOyQIOxZfbiIbAHbm3M8hj73K # QWcCR5JavgkwUmncFHESaQf4Drqs+/1L1QIDAQABo4IBSTCCAUUwHQYDVR0OBBYE # FAuO8UzF7DcH0mmsF4XQxxHQvS2jMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWn # G1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEw # KDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFt # cCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAww # CgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCb # u9rTAHV24mY0qoG5eEnImz5akGXTviBwKp2Y51s26w8oDrWor+m00R4/3BcDmYlU # K8Nrx/auYFYidZddcUjw42QxSStmv/qWnCQi/2OnH32KVHQ+kMOZPABQTG1XkcnY # PUOOEEor6f/3Js1uj4wjHzE4V4aumYXBAsr4L5KR8vKes5tFxhMkWND/O7W/RaHY # wJMjMkxVosBok7V21sJAlxScEXxfJa+/qkqUr7CZgw3R4jCHRkPqQhMWibXPMYar # /iF0ZuLB9O89DMJNhjK9BSf6iqgZoMuzIVt+EBoTzpv/9p4wQ6xoBCs29mkj/EIW # Fdc+5a30kuCQOSEOj07+WI29A4k6QIRB5w+eMmZ0Jec0sSyeQB5KjxE51iYMhtlM # rUKcr06nBqCsSKPYsSAITAzgssJD+Z/cTS7Cu35fJrWhM9NYX24uAxYLAW0ipNtW # ptIeV6akuZEeEV6BNtM3VTk+mAlV5/eC/0Y17aVSjK5/gyDoLNmrgVwv5TAaBmq/ # wgRRFHmW9UJ3zv8Lmk6mIoAyTpqBbuUjMLyrtajuSsA/m2DnKMO0Qiz1v+FSVbqM # 38J/PTlhCTUbFOx0kLT7Y/7+ZyrilVCzyAYfFIinDIjWlM85tDeU8ZfJCjFKwq3D # sRxV4JY18xww8TTmod3lkr9NqGQ54LmyPVc+5ibNrjCCB3EwggVZoAMCAQICEzMA # AAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290 # IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMw # MDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0G # CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3u # nAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1 # jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZT # fDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+ # jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c # +gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+ # cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C6 # 26p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV # 2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoS # CtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxS # UV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJp # xq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkr # BgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0A # XmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYI # KwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9S # ZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIE # DB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV # HSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVo # dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29D # ZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAC # hj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1 # dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwEx # JFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts # 0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9I # dQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYS # EhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMu # LGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT9 # 9kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2z # AVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6Ile # T53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6l # MVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbh # IurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3u # gm2lBRDBcQZqELQdVTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9z # b2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO # OkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBHJY2Fv+GhLQtRDR2vIzBaSv/7LKCBgzCB # gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUA # AgUA6Rj/wzAiGA8yMDIzMTIwNTAxNDc0N1oYDzIwMjMxMjA2MDE0NzQ3WjB0MDoG # CisGAQQBhFkKBAExLDAqMAoCBQDpGP/DAgEAMAcCAQACAgI3MAcCAQACAhQ/MAoC # BQDpGlFDAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA # AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAB4OIDT1ZqC1ZD2g # ezvM8gnGarq2OuCXwM9UVdPkBmwrfcxtqDTsFJJovg1KR7biOMVwhBKmu2Agwzbu # rQyrNoX+4xecVefNzrjG4mz8tT4GCJVv9Vqfx0+6YxZ4OdUFZ04gQb6ojbB5VSi6 # 7ecW004Gkiat//RyGi1YgqS9jrzGpxlk2uOZRpGIO1mChbq0VwlaML2vjFO/lIJx # ybVL5PaQogylDFKwYfRA8ryIRflnDfpftNWTQo6kkOAmmq09tuNtc+G10GCoDwu2 # 6IlGsqtXNGlnDdsn3yR0w+K0yrrBqhfKzWiIMt7CMYgfSSexO6scVdtYErXlS2aX # aR4eamsxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MAITMwAAAdGyW0AobC7SRQABAAAB0TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZI # hvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDHY/X+ezIGQggz # 9pojwQRjS68POUKdJovFX3mw/ir8BzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQw # gb0EIMy8YXkCALv57c5sRhrPTub1q4TwJ6oVA36k8IiI/AcMMIGYMIGApH4wfDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHRsltAKGwu0kUAAQAAAdEw # IgQgHWwSZv5Ko32MHb3PP7h5Z7Gr6Y9ZZhsEAyVFaP8fyqIwDQYJKoZIhvcNAQEL # BQAEggIAiwcCYwstHCEEHsQKWdL36qcw50VJd/+gBLvYbdV0Hs6QDlNLfbwEPK/5 # NcrwOHUuz3bE6Ofx4kEnpWOlHfIG9vA0GyDTK7+o5usEh4TEZrQy3rzo5T0Hq1Zf # beg6uU/hXWenkwXKaICS/PYHl6HJaaT4hNWNbAzLpfhk23ReQ4xxv5OKJCuDM9gP # MVKpmjMLWOCUYJTmC3a4PoZHV2gezrxACfLgD9ZDUfO6i2XQOdu7WsU60a0Ss9XS # 5nkI2MMkgm6cESF7FsSdbdhHBM3x/lnSBye2g0HZ3uMTbmpY9V+H8+4IWDJVrMI2 # rOc/oB95K6QbUvNvQ7h9XXyHm/8MB3EZ7WhQTEC1EynW5gU70lfGp9rmNQ7Omx4N # e3DUzC8RlEqr3i2NAFauVH1mhB9iZEr7csV6tbtPR7SMUzAqyyt55Cr67fipd0M1 # mw6hwGrrflc4T2eeWXaa/BpVwF6llCPyPLfONQx7y4CcHO2S9fMtp8+kS+t2surn # XxoK0P0JfqzKjX/4m3l1H9yHbk/dEPqi3YNrDNBfpbOoV9V3UHN2gFyVh9+/vXYD # iVSLg7Xw50hlhzMrN9s1d/kYQZ9IQt/rHjcZ9RLtNIFKajoCN3Qr+HBnnBRlRZt+ # TIQMATB0KSux4OK2pk8LfgHQKnPZten+laRVH1xKkTh/7alQYjA= # SIG # End signature block