Installing Windows Defender on Server 2012 R2 is a common challenge faced by system administrators and IT professionals. Windows Defender is a built-in antivirus solution offered by Microsoft that provides real-time protection against viruses, malware, and other threats. While Server 2012 R2 does not come with Windows Defender pre-installed, there are methods available to install and configure it on the server.
This blog post aims to provide a detailed guide on how to install Windows Defender on Server 2012 R2. We will explore different methods, their pros and cons, as well as provide alternative solutions and bonus tips to enhance the security of your server.
The Challenge of Installing Windows Defender on Server 2012 R2
Installing Windows Defender on Server 2012 R2 can be challenging due to the following reasons:
- Lack of pre-installed Windows Defender: Unlike Windows client operating systems, Server 2012 R2 does not come with Windows Defender pre-installed. Therefore, additional steps are required to install and enable it on the server.
- Incompatibility with other antivirus products: If you already have an antivirus product installed on your server, there might be compatibility issues with Windows Defender. It is important to consider this before proceeding with the installation.
- Minimal documentation: Microsoft does not provide detailed documentation on how to install Windows Defender on Server 2012 R2, which can make the process confusing and time-consuming for administrators.
Now let’s explore the different methods to install Windows Defender on Server 2012 R2.
Video Tutorial:
Method 1: Using PowerShell
Method: PowerShell is a powerful command-line tool in Windows that allows users to automate administrative tasks. We can use PowerShell commands to install and enable Windows Defender on Server 2012 R2.
Pros:
1. PowerShell offers a streamlined and efficient way to install Windows Defender.
2. It allows for automation, making it easier to deploy Windows Defender on multiple servers.
3. PowerShell commands are well-documented and widely supported by the Microsoft community.
Cons:
1. Requires administrative privileges to run PowerShell commands.
2. Users should have a basic understanding of PowerShell syntax and commands.
3. In some cases, PowerShell scripts can be blocked by security policies, preventing the installation of Windows Defender.
Now, let’s go through the steps to install Windows Defender using PowerShell:
1. Open PowerShell with administrator privileges.
2. Run the following command to install Windows Defender:
Install-WindowsFeature -Name Windows-Defender, GUI-Management-Tools
3. Once the installation is complete, restart the server for the changes to take effect.
Method 2: Using Server Manager
Method: Server Manager is a management console in Windows that allows administrators to manage server roles and features. We can use Server Manager to install and enable Windows Defender on Server 2012 R2.
Pros:
1. Server Manager provides a user-friendly interface for installing and managing server roles and features.
2. It is a built-in tool in Windows Server, eliminating the need for additional downloads or installations.
3. Server Manager supports remote management, making it easier to install Windows Defender on multiple servers.
Cons:
1. Requires administrative privileges to access and use Server Manager.
2. The graphical interface of Server Manager might be overwhelming for users who are not familiar with it.
3. Server Manager can be slower compared to PowerShell when installing large features like Windows Defender.
Here are the steps to install Windows Defender using Server Manager:
1. Open Server Manager.
2. Click on “Manage” and select “Add Roles and Features”.
3. Follow the on-screen instructions in the “Add Roles and Features” wizard, ensuring that you select “Windows Defender” as a feature to install.
4. Once the installation is complete, restart the server for the changes to take effect.
Method 3: Using DISM (Deployment Image Servicing and Management)
Method: DISM is a command-line tool in Windows that allows users to service and manage Windows images offline or in a running operating system. We can use DISM to install Windows Defender on Server 2012 R2.
Pros:
1. DISM provides a flexible and versatile approach to install Windows components, including Windows Defender.
2. It is a built-in tool in Windows, eliminating the need for additional downloads or installations.
3. DISM commands are well-documented and widely supported by the Microsoft community.
Cons:
1. Requires administrative privileges to run DISM commands.
2. Users should have a basic understanding of DISM syntax and commands.
3. DISM commands can be complex and may require additional parameters for successful installation.
To install Windows Defender using DISM, follow these steps:
1. Open Command Prompt with administrator privileges.
2. Run the following command to check the available editions of Windows Defender:
Dism /online /Get-Features /Format:Table
3. Identify the feature name for Windows Defender by locating “Windows-Defender” in the output of the previous command.
4. Run the following command to install Windows Defender:
Dism /online /Enable-Feature /FeatureName:Windows-Defender
5. Once the installation is complete, restart the server for the changes to take effect.
Method 4: Using Group Policy
Method: Group Policy is a feature in Windows that allows administrators to manage user and computer settings centrally. We can use Group Policy to enable and configure Windows Defender on Server 2012 R2.
Pros:
1. Group Policy provides a centralized and scalable way to enable Windows Defender on multiple servers.
2. It allows for granular control over the configuration settings of Windows Defender.
3. Group Policy settings can be easily modified and updated as per organizational requirements.
Cons:
1. Requires administrative privileges to access and modify Group Policy settings.
2. Users should have a good understanding of Group Policy concepts and settings.
3. Group Policy changes might take time to propagate to all network resources.
To enable Windows Defender using Group Policy, follow these steps:
1. Open Group Policy Management console.
2. Create or select a Group Policy Object (GPO) linked to the desired Organizational Unit (OU) or domain.
3. Navigate to “Computer Configuration” > “Policies” > “Administrative Templates” > “Windows Components” > “Windows Defender”.
4. Double-click on “Turn off Windows Defender” to open the policy settings.
5. Select the “Disabled” option to enable Windows Defender.
6. Apply the changes to the desired OU or domain.
7. Update the Group Policy on target servers using the following command:
gpupdate /force
Alternatives: What to Do If You Can’t Install Windows Defender
If you cannot install Windows Defender on Server 2012 R2 for any reason, here are some alternative solutions you can consider:
1. Install a third-party antivirus software: There are various antivirus solutions available in the market that are compatible with Server 2012 R2. Research and choose a reputable antivirus software that meets your organization’s security requirements.
2. Utilize Windows Defender Offline: If you suspect a malware infection on your server, you can use Windows Defender Offline, a bootable antivirus scanner that can detect and remove malware before the operating system loads.
3. Implement Microsoft Security Essentials: Although Microsoft discontinued support for Microsoft Security Essentials on Server 2012 R2, you can still consider using it as an alternative antivirus solution. However, keep in mind that it does not offer the same level of features and protection as Windows Defender.
Bonus Tips
Here are some bonus tips to enhance the security of your Server 2012 R2:
1. Keep the server up to date: Regularly install Windows updates, including security patches, to protect against new threats and vulnerabilities.
2. Enable Windows Firewall: Configure and enable the built-in Windows Firewall to add an extra layer of protection to your server.
3. Implement strong password policies: Enforce strong password policies and regularly prompt users to change their passwords to prevent unauthorized access.
5 FAQs about Installing Windows Defender on Server 2012 R2
Q1: Can I install Windows Defender alongside another antivirus software on Server 2012 R2?
A: It is generally not recommended to run multiple antivirus software on the same server. They can conflict with each other and cause performance issues. It is best to choose one antivirus solution and disable or uninstall any other competing antivirus software.
Q2: Is Windows Defender sufficient for protecting my Server 2012 R2 from all types of threats?
A: While Windows Defender provides basic protection against common threats, it is recommended to complement it with other security measures such as regular updates, strong passwords, and a secure network infrastructure. Consider additional security solutions based on your specific requirements and risk profile.
Q3: Can I install Windows Defender on Server Core installations of Server 2012 R2?
A: Unfortunately, Windows Defender is not available for Server Core installations of Server 2012 R2. It is only available for installations with a graphical user interface (GUI).
Q4: How often should I run Windows Defender scans on my Server 2012 R2?
A: It is recommended to perform regular scans using Windows Defender to ensure the ongoing security of your server. The frequency of scans can vary depending on your organization’s security requirements and risk profile. Consider scheduling weekly or monthly scans to detect and mitigate any potential threats.
Q5: Can I install Windows Defender on older versions of Windows Server, such as Server 2008 R2?
A: Windows Defender is not available for older versions of Windows Server, such as Server 2008 R2. It was introduced in Windows Server 2016 and later versions. For older versions, consider using alternative antivirus solutions or upgrading to a supported version of Windows Server.
In Conclusion
Installing Windows Defender on Server 2012 R2 can be a challenge, but with the methods and tips outlined in this blog post, you can successfully implement this antivirus solution on your server. Whether you choose to use PowerShell, Server Manager, DISM, or Group Policy, make sure to consider the pros and cons of each method and select the one that best suits your requirements.
If you cannot install Windows Defender, explore alternative antivirus solutions or utilize Windows Defender Offline to mitigate malware threats. Additionally, follow the bonus tips provided to enhance the overall security of your Server 2012 R2. Remember to keep your server up to date, enable Windows Firewall, and implement strong password policies to ensure a robust defense against malicious activities.
How to Configure Windows Defender on Windows Server 2012 R2
If you are using Windows Server 2012 R2, it is essential to have a robust security solution in place to protect your server from malware and other threats. Windows Defender, Microsoft’s built-in antivirus software, can help you safeguard your server from various security risks.
In this article, we will guide you through the process of configuring Windows Defender on Windows Server 2012 R2, ensuring that your server is well-protected against malware and other malicious activities.
Step 1: Open Windows Defender
The first step in configuring Windows Defender on your Windows Server 2012 R2 is to open the program. To do this, follow these steps:
- Click on the Start button and type ‘Windows Defender’ in the search bar.
Once you have located Windows Defender, click on the program to open it.
Step 2: Update Windows Defender
Before configuring Windows Defender, it is crucial to make sure that the program is up-to-date. To update Windows Defender, follow these steps:
- Click on the ‘Update’ tab in Windows Defender.
Windows Defender will check for updates and install them if there are any available.
Step 3: Configure Windows Defender Settings
Once Windows Defender is up-to-date, you can proceed to configure its settings to ensure optimal protection for your Windows Server 2012 R2. Some key settings to consider include:
- Real-time protection: Turn on real-time protection to scan new files and programs as they are downloaded or accessed on your server.
- Cloud-based protection: Enable cloud-based protection to access the latest threat information and protect your server from emerging threats.
- Automatic sample submission: Allow Windows Defender to automatically submit detected samples to Microsoft for analysis and improve threat detection.
By configuring these settings, you can enhance the security of your Windows Server 2012 R2 and protect it from various security threats.
Step 4: Perform Regular Scans
In addition to real-time protection, it is essential to perform regular scans on your Windows Server 2012 R2 to detect and remove any existing malware. To schedule a scan, follow these steps:
- Click on the ‘Scan’ tab in Windows Defender.
- Select the type of scan you want to perform (Quick scan, Full scan, or Custom scan).
- Choose a convenient time to schedule the scan, such as during off-peak hours.
Regularly scanning your server will help you identify and eliminate any potential security threats, keeping your system secure and stable.
Conclusion
Configuring Windows Defender on Windows Server 2012 R2 is essential to ensure the security and stability of your server. By following the steps outlined in this article, you can enhance the protection of your server against malware and other security threats.
Remember to keep Windows Defender up-to-date and perform regular scans to maintain a secure computing environment for your Windows Server 2012 R2.
#Antivirus #configuration #cybersecurity #Windows Defender #Windows Server 2012 R2
Since April 11th, 2022, the new unified Microsoft Defender for Endpoint solution is generally available for Server 2016 and Server 2016. The unified Microsoft Defender for Endpoint solution enables more features that were previously only available on Windows Server 2019 and later. The new modernized solution stack enables more security features for Server 2012R2 and Server 2016.
August 18, 2022; Blog completely rewritten based on latest announcement and improvements; included MECM/ Defender for Cloud and updated information since GA release.
Previous method
Previously there was a large gap between the latest Server 2019 build and the down-level OS systems. In comparison with Server 2019, the onboarding process was quite complex with the Microsoft Monitoring Agent. The MMA agent was required as the EDR sensor wasn’t built-in, for Server 2016 en Server 2012R2.
Server 2016 is by default installed with Microsoft Defender Antivirus. For Server 2012R2 there was no installed AV by default, and you had to install System Centre Endpoint Protection (SCEP).
With the Microsoft Monitoring Agent and Defender AV/SCEP, there were still some missing protection features – like Attack Surface Reduction, Automated Investigation, Network Protection, and many more protection features.
Now the good news. Currently in general availability is the new unified solution for Server 2012R2 and Server 2016. The new unified solution reduces complexity by removing dependencies and installation steps – and more important no more SCEP, MMA, and all the latest security features available.
The new unified package brings the following major improvements directly to the new Defender for Endpoint solution:
- Attack Surface Reduction rules
- Network protection
- Controlled Folder Access
- Potentially Unwanted Application blocking
- Improved detection capabilities
- Response capacibiliteits
- EDR in block mode
- Automated Investigation and Response (AIR)
- Tamper Protection
- Live Response
Overview of all features
New Defender for Endpoint agent
The new unified solution is available in one single package for all the down-level systems. Summary for Windows Server systems:
Built-in Defender AV and EDR sensor. Only onboarding package needed for onboard
- Windows Server 1803
- Windows Server 2019
- Windows Server 2022
New unified MDE installation package and onboarding package are needed for onboard
- Windows Server 2012R2
- Windows Server 2016
Windows Server 2008R2 is currently only supported for onboarding using the legacy MMA-agent and SCEP. Advised is to migrate Server 2008R2, which makes it possible to manage all systems with a single solution and the same configuration. SCEP is legacy and missed critical protection capabilities provided by Microsoft.
Microsoft announced in the last couple of months multiple improvements which make the installation of the new unified agent way more easier from products like; Microsoft Defender for Cloud and Microsoft Endpoint Configuration Manager. Currently it is possible to migrate all existing MMA-based Defender for Endpoint solutions to the new unified agent.
Download new Defender for Endpoint agent
The new Defender for Endpoint installation and onboarding packages can be downloaded directly from the security.microsoft.com portal.
- Go to Security.Microsoft.com
- Open Settings -> Endpoints -> Onboarding
- Select Windows Server 2012R2 and Server 2016
- Select Deployment method; Group Policy or one of the other deployment methods
Download the installation package (md4ws.msi) and onboarding package (WindowsDefenderATPOnboardingScript.cmd).
NOTE: The package is updated monthly with new improvements. Make sure to download always the latest package before usage. The package contains the Defender product version and additional improvements.
Prerequisites for Windows Server 2012R2 and Windows Server 2016
For the new unified solution, fewer requirements are needed in comparison with the legacy method. The following requirements are needed before installing the new Defender solution.
Network
Ensure connectivity requirements are configured. Network requirements are the same as Windows Server 2019. Important: There is no OMS Gateway support for the new agent. Follow the instructions for configuring the network connectivity. Download here the spreadsheet including all URLs (WW + Defender Geography)
Server 2012R2 requirements
When the latest monthly rollup is installed – no additional prerequisites are needed for Server 2012R2. During the installer the package checks for the following updates:
- Update for customer experience and diagnostic telemetry / KB3080149
- Update for Universal C Runtime in Windows / KB2999226
Make sure both updates are correctly installed. Important; when already using SCEP it is needed to remove the SCEP agent first. Recommended is to use the installer script which automatically removes SCEP and installs additional prerequisites. (more information later in this blog)
Server 2016 requirements
For Server 2016 the build-in Defender server must be enabled. For correctly installing the new agent make sure the following prerequisites are in place.
- The Servicing Stack Update (SSU) from September 14, 2021 or later must be installed. (latest version recommended)
- The Latest Cumulative Update (LCU) from September 20, 2018 or later must be installed. (latest version recommended)
- Enable build-in Defender Antivirus
- Update the latest Defender Antivirus platform version (download package from Microsoft Update Catalog or MMPC)
Manual installation
Installation of the new Defender for Endpoint agent is simple when the prerequisites are correctly in place. md4ws.msi supports additional parameters:
Silent installation:
Msiexec /i md4ws.msi /quiet
Silent installation + configuration in passive mode:
Msiexec /i md4ws.msi /quiet FORCEPASSIVEMODE=1
Passive mode
If there is a default non-Microsoft antivirus/antimalware solution available it is directly possible to enable the passive mode for the Microsoft Defender Antivirus components. using FORCEPASSIVEMODE=1
For making sure Defender for Endpoint is correctly configured in passive mode after the initial onboarding the “ForceDefenderPassiveMode” need to be added.
- Path:
HKLM\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection
- Name:
ForceDefenderPassiveMode
- Type:
REG_DWORD
- Value:
1
During the installation the following error can be visible; Please update Windows Defender. When the error is showing make sure the latest Defender platform update is installed for Server 2016.
For checking the current platform version. Use the PowerShell command: Get-MpComputerStatus and check the value: AMProductVersion
Scripted installation
Microsoft published a GitHub installation script that automated most of the migration/ installation steps. The script can help with automating the following steps:
- Remove the OMS workspace for Microsoft Defender for Endpoint
- Remove System Center Endpoint Protection (SCEP) client if installed on Server 2012R2
- Download and install (Windows Server 2012 R2) prerequisites if required.
- Trying to active Defender on Windows Server 2016
- If Defender is installed and running but outdated, it updates to the latest platform version on Windows Server 2016 when the state is upgradeable (see prerequisites)
- Install Microsoft Defender for Endpoint
- Onboard Defender for Endpoint
More information and download: upgrade script | Github
Install and remove the MMA agent (replace <WORKSPACE_ID> with the actual MMA agent ID)
.\Install.ps1 -RemoveMMA <WORKSPACE_ID> -OnboardingScript ".\WindowsDefenderATPOnboardingScript.cmd"
Install and configure passive mode
.\Install.ps1 -OnboardingScript ".\WindowsDefenderATPOnboardingScript.cmd" -Passive
-Passive
The parameter -Passive enabled Defender Antivirus in passive mode. Make sure to set the “ForceDefenderPassiveMode” registry key on all servers for getting the passive mode enabled after the Defender for Endpoint onboarding.
Path: HKLM\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection
Name: ForceDefenderPassiveMode
Type: REG_DWORD
Value: 1
Passive mode can be disabled when changing the value from 1 to 0. Passive mode can be used when migrating from non-Microsoft antivirus solutions.
The result after installing and onboarding with the installer script:
For Server 2012R2 and Server 2016 the Sense service is enabled after completing the MDE onboarding script.
Install/Migrate in Defender for Cloud
When using Defender for Cloud for Server 2012R2 and Server 2016 onboarding the new Defender for Servers Plan 1/2 integration can be used for automatically installing or migrating from the MMA-based Defender solution.
MDE integration with Defender for Servers P2 is by default the new solution for new Defender for Cloud activations or new subscriptions. When Defender for Servers P2 was enabled before June 20th, 2022, or MDE integration was enabled before June 20th, 2022, the new Enable Unified Solution button is visible.
The following options are available:
- Migrate all machines in subscriptions using Enable Unified Solution button
- Testing particular machines using REST API without enabling the complete subscription
- Deploying using Azure Policy
One of the following situations is mostly available in environments;
- Defender for Servers is already enabled and Microsoft Defender for Endpoint was deployed (MMA solution)
- Defender for Servers integration was never enabled
To enable the MDE unified solution in existing subscriptions you can easily opt-in to the unified solution on the subscription environment settings/integrations page. For opening the settings:
- Go to Defender for Cloud
- Go to Environment settings and select the subscription
- Go to Integrations
The button Enable unified solution is visible when the MDE integration was enabled before June 20th, 2022.
After enablement, it can take up to 12 hours before the extension is installed and MMA is replaced with the new Defender solution. Microsoft uses a version of the Defender script for uninstalling SCEP and installing the new solution. View the following blog for more information; How to upgrade from MMA-based Defender for Endpoint to MDE unified solution in Defender for Cloud?
Install using Microsoft Endpoint Configuration Manager
Since update 2207 for Microsoft Endpoint Configuration Manager current branch the improved Defender for Endpoint onboarding for Windows Server 2012R2 and Windows Server 2016 is finally available.
Since version 2207 Configuration Manager version 2207 supports the automatic installation of the new unified Microsoft Defender for Endpoint agent.
Migration is possible using the Client Settings which are used previously for MMA/ SCEP.
Migrating from SCEP
- Change the Client settings used for Endpoint Protection and change from Microsoft Monitoring Agent MMA (legacy) to MDE client (recommended).
- Configure the Defender for Endpoint onboarding file downloaded from security.microsoft.com
- Upload the .onboarding file and configure file sample collection
Client settings can be founded under Endpoint Protection
Go to Assets and Compliance > Endpoint Protection > Microsoft Defender ATP Policies and select Create Microsoft Defender ATP Policy and upload the downloaded onboarding file from security.microsoft.com. (use the Deployment method; Microsoft Endpoint Configuration Manager current branch and later)
Note: For Server 2008R2 and older MDE-supported down-level systems it is still needed to use Microsoft Monitoring Agent (MMA) (legacy) in the Client Settings and use the workspace key + workspace ID. Server 2012R2/ Server 2016 works via the .onboardingfile and the new MDE client setting; where Server 2008R2 works only with the MMA configuration.
Differences in portal
After onboarding, the new features are visible on the device page. The following actions are now available in comparison with the legacy MMA situation:
- Isolate devices
- Run Antivirus Scan
- Collect Investigation Package
- Initiate Live Response Session (now supported)
- Initiate Automated Investigation (now supported)
Server 2016 ( legacy MMA)
Server 2016 (new MDE unified solution agent)
Configuration
With the new Unified Solutions all the Group Policy, PowerShell commands, and other management options similar to Server 2019 are available for Server 2012R2 and Server 2016. You can use the Group Policy templates for Server 2019 to manage Defender on Windows Server 2012R2 and 2016. The following methods are available:
- Microsoft Endpoint Configuration Manager
- GPO
- PowerShell
- Security Management feature in MEM (advised and supported for the new agent)
View the following blog for more in-depth MEM for MDE management details; Managing Microsoft Defender for Endpoint with the new Security Management feature in MEM
Configuration – Attack Surface Reduction
Attack Surface Reduction is with the new unified solution available. Based on the Server OS there are some differences in the ASR support.
ASR Rule | ID | Server 2016 | Server 2012R2 |
Block abuse of exploited vulnerable signed drivers | 56a863a9-875e-4185-98a7-b882c64b5ce5 | Yes | Yes |
Block Adobe Reader from creating child processes | 7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c | Yes | Yes |
Block all Office applications from creating child processes | d4f940ab-401b-4efc-aadc-ad5f3c50688a | Yes | Yes |
Block credential stealing from the Windows local security authority subsystem (lsass.exe) | 9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2 | Yes | Yes |
Block executable content from email client and webmail | be9ba2d9-53ea-4cdc-84e5-9b1eeee46550 | Yes | Yes |
Block executable files from running unless they meet a prevalence, age, or trusted list criterion | 01443614-cd74-433a-b99e-2ecdc07bfc25 | Yes | Yes |
Block execution of potentially obfuscated scripts | 5beb7efe-fd9a-4556-801d-275e5ffc04cc | Yes | Yes |
Block JavaScript or VBScript from launching downloaded executable content | d3e037e1-3eb8-44c8-a917-57927947596d | Yes | No |
Block Office applications from creating executable content | 3b576869-a4ec-4529-8536-b80a7769e899 | Yes | Yes |
Block Office applications from injecting code into other processes | 75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84 | Yes | Yes |
Block Office communication application from creating child processes | 26190899-1602-49e8-8b27-eb1d0a1ce869 | Yes | Yes |
Block process creations originating from PSExec and WMI commands | d1e49aac-8f56-4280-b9ba-993a6d77406c | Yes | Yes |
Block untrusted and unsigned processes that run from USB | b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4 | Yes | Yes |
Use advanced protection against ransomware | c1db55ab-c21a-4637-bb3f-a12568109d35 | Yes | Yes |
Block Win32 API calls from Office macros | 92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b | No | No |
Block persistence through WMI event subscription | e6db77e5-3df2-4cf1-b95a-636979351e5b | No | No |
Configuration – Network Protection
For enabling Network Protection, additional configuration is required. For Server 2012R2 and Server 2016 additional configuration is needed for Network protection. Use the below PowerShell commands for enabling the NetworkProtection feature.
Set-MpPreference -EnableNetworkProtection Enabled
Set-MpPreference -AllowNetworkProtectionOnWinServer 1
Set-MpPreference -AllowNetworkProtectionDownLevel 1
Set-MpPreference -AllowDatagramProcessingOnWinServer 1
Set-MpPreference -AllowSwitchToAsyncInspection $True
Update maintenance
Important is to apply the latest updates which are supported for Server 2012R2 and Server 2016. KB5005292 is released for updating Microsoft Defender for Endpoint. This update services the EDR sensor included in the new Microsoft Defender for Endpoint unified solution and is needed for Server 2016 / 2012R2 based on the EDR sensor component. Important: Make sure the new update is deployed to receive features and fixes.
Result: Automated Investigation & Response AIR
Automated Investigation and response (AIR) is working and exactly the same in comparison with Windows 10+/ Server 2019+
Server 2016:
Server 2012R2:
Result: Live Response
Live response is supported and working exactly the same in comparison with Windows 10/ Server 2019.
Server 2016:
Server 2012R2:
Good to know
- Operating system upgrades aren’t supported. Offboard then uninstall before upgrading
- Automatic exclusions for server roles aren’t supported on Windows Server 2012 R2
- No UI is available on Windows Server 2012R2 (use PowerShell cmdlets)
More limitations can be founded here
Conclusion
The new Defender for Endpoint unified solution for Server 2012R2 and 2016 works way easier in comparison with the legacy MMA onboarding method. The new unified solution package reduces complexity by removing dependencies and installation steps. It also standardizes capabilities and functionality for the complete Defender for Endpoint stack.
Oh.. and it is always a good idea to upgrade Server 2012R2 and 2016 to one of the latest Server versions. Don’t stay behind if you can upgrade easily to Server 2019 or higher.
Sources
- Microsoft: Defending Windows Server 2012 R2 and 2016
- Jeffreyappel.nl: How to upgrade from MMA-based Defender for Endpoint to MDE unified solution in Defender for Cloud
- GitHub: MDE installer script
- Microsoft: Onboard Windows servers to the Microsoft Defender for Endpoint service
- Microsoft: Attack Surface Reduction (ASR) rules
Install Defender
https://github.com/microsoft/mdefordownlevelserver
<#
.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 "<ScriptBlock>") {
[int] $lineNumber = $currentPS.ScriptLineNumber
if ($null -ne $currentPS.ScriptName) {
$scriptFullName = $currentPS.ScriptName
}
else {
if ($null -eq (Get-Variable VMPosition -ErrorAction:Ignore)) {
$scriptFullName = '<interactive>'
}
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}<space>FLAGS<space>LEVEL" -Encoding:ascii
$providers | ForEach-Object {
# Any line that starts with '#','*',';' is commented out
# '-' in front of a provider disables it.
# {PROVIDER_GUID}<space>FLAGS<space>LEVEL
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
Loading…