-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e74460d
commit 8beaf15
Showing
23 changed files
with
2,172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
#Requires -PSEdition Desktop | ||
#Requires -Version 5 | ||
#Requires -RunAsAdministrator | ||
<# | ||
.SYNOPSIS | ||
Detect whether the UE-V service is enabled | ||
.DESCRIPTION | ||
Detect whether the UE-V service is enabled and returns status code for Proactive Remediations | ||
.NOTES | ||
Author: Aaron Parker | ||
Twitter: @stealthpuppy | ||
.LINK | ||
https://stealthpuppy.com/user-experience-virtualzation-intune/ | ||
.EXAMPLE | ||
Set-Uev.ps1 | ||
#> | ||
[CmdletBinding(SupportsShouldProcess = $False, HelpURI = "https://github.com/aaronparker/intune/blob/main/Uev/README.md")] | ||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Output required by Proactive Remediations.")] | ||
param ( | ||
[Parameter(Mandatory = $false)] | ||
[System.String] $Uri = "https://stpydeviceause.blob.core.windows.net/uev/?comp=list", | ||
|
||
[Parameter(Mandatory = $false)] | ||
[System.String] $CustomTemplatesPath = "$env:ProgramData\Microsoft\UEV\CustomTemplates" | ||
) | ||
|
||
#region Functions | ||
function Get-AzureBlobItem { | ||
<# | ||
.SYNOPSIS | ||
Returns an array of items and properties from an Azure blog storage URL. | ||
.DESCRIPTION | ||
Queries an Azure blog storage URL and returns an array with properties of files in a Container. | ||
Requires Public access level of anonymous read access to the blob storage container. | ||
Works with PowerShell Core. | ||
.NOTES | ||
Author: Aaron Parker | ||
Twitter: @stealthpuppy | ||
.PARAMETER Url | ||
The Azure blob storage container URL. The container must be enabled for anonymous read access. | ||
The URL must include the List Container request URI. See https://docs.microsoft.com/en-us/rest/api/storageservices/list-containers2 for more information. | ||
.EXAMPLE | ||
Get-AzureBlobItems -Uri "https://aaronparker.blob.core.windows.net/folder/?comp=list" | ||
Description: | ||
Returns the list of files from the supplied URL, with Name, URL, Size and Last Modified properties for each item. | ||
#> | ||
[CmdletBinding(SupportsShouldProcess = $False)] | ||
[OutputType([System.Management.Automation.PSObject])] | ||
param ( | ||
[Parameter(ValueFromPipeline = $True, Mandatory = $True, HelpMessage = "Azure blob storage URL with List Containers request URI '?comp=list'.")] | ||
[ValidatePattern("^(http|https)://")] | ||
[System.String] $Uri | ||
) | ||
|
||
begin {} | ||
process { | ||
# Get response from Azure blog storage; Convert contents into usable XML, removing extraneous leading characters | ||
try { | ||
$iwrParams = @{ | ||
Uri = $Uri | ||
UseBasicParsing = $True | ||
ContentType = "application/xml" | ||
ErrorAction = "Stop" | ||
} | ||
$list = Invoke-WebRequest @iwrParams | ||
} | ||
catch [System.Net.WebException] { | ||
Write-Warning -Message ([System.String]::Format("Error : {0}", $_.Exception.Message)) | ||
throw $_.Exception.Message | ||
} | ||
catch [System.Exception] { | ||
Write-Warning -Message "failed to download: $Uri." | ||
throw $_.Exception.Message | ||
} | ||
if ($Null -ne $list) { | ||
[System.Xml.XmlDocument] $xml = $list.Content.Substring($list.Content.IndexOf("<?xml", 0)) | ||
|
||
# Build an object with file properties to return on the pipeline | ||
$fileList = New-Object -TypeName System.Collections.ArrayList | ||
foreach ($node in (Select-Xml -XPath "//Blobs/Blob" -Xml $xml).Node) { | ||
$PSObject = [PSCustomObject] @{ | ||
Name = $($node | Select-Object -ExpandProperty "Name") | ||
Uri = $($node | Select-Object -ExpandProperty "Url") | ||
Size = $($node | Select-Object -ExpandProperty "Size") | ||
LastModified = $($node | Select-Object -ExpandProperty "LastModified") | ||
} | ||
$fileList.Add($PSObject) | Out-Null | ||
} | ||
if ($Null -ne $fileList) { | ||
Write-Output -InputObject $fileList | ||
} | ||
} | ||
} | ||
} | ||
|
||
function Test-WindowsEnterprise { | ||
try { | ||
Import-Module -Name "Dism" | ||
$edition = Get-WindowsEdition -Online -ErrorAction "SilentlyContinue" | ||
} | ||
catch [System.Exception] { | ||
Write-Error -Message "Failed to run Get-WindowsEdition. Defaulting to False." | ||
} | ||
if ($edition.Edition -eq "Enterprise") { | ||
return $True | ||
} | ||
else { | ||
return $False | ||
} | ||
} | ||
function Write-ToEventLog ($Message) { | ||
switch -Regex ($Message) { | ||
"^Information" { | ||
$EntryType = "Information" | ||
$Number = 0 | ||
} | ||
"^Warning" { | ||
$EntryType = "Warning" | ||
$Number = 1 | ||
} | ||
"^Error" { | ||
$EntryType = "Error" | ||
$Number = 2 | ||
} | ||
default { | ||
$EntryType = "Information" | ||
$Number = 0 | ||
} | ||
} | ||
$params = @{ | ||
LogName = "Application" | ||
Source = "UevProactiveRemediation" | ||
EventID = (8000 + $Number) | ||
EntryType = $EntryType | ||
Message = $Message | ||
ErrorAction = "SilentlyContinue" | ||
} | ||
Write-EventLog @params | ||
} | ||
#endregion | ||
|
||
# Create a new event log source | ||
$params = @{ | ||
LogName = "Application" | ||
Source = "UevProactiveRemediation" | ||
ErrorAction = "SilentlyContinue" | ||
} | ||
New-EventLog @params | ||
|
||
# If running Windows 10/11 Enterprise | ||
if (Test-WindowsEnterprise) { | ||
|
||
# If the UEV module is installed | ||
if (Get-Module -ListAvailable -Name "UEV") { | ||
|
||
# Detect the UE-V service | ||
Import-Module -Name "UEV" | ||
$status = Get-UevStatus | ||
if ($status.UevEnabled -eq $True) { | ||
$Message = "Detection result:" | ||
$Result = 0 | ||
|
||
try { | ||
# Retrieve the list of templates from the Azure Storage account, filter for .XML files only | ||
$SrcTemplates = Get-AzureBlobItem -Uri $Uri | Where-Object { $_.Uri -match ".*.xml$" } | ||
} | ||
catch [System.Exception] { | ||
$Message += "`nError at $Uri with $($_.Exception.Message)." | ||
$Result = 1 | ||
} | ||
|
||
# Validate the local copy of the custom templates against the Azure Storage account | ||
$CustomTemplates = $(Get-ChildItem -Path $CustomTemplatesPath -Filter "*.xml" -ErrorAction "SilentlyContinue").Name | ||
$params = @{ | ||
ReferenceObject = $SrcTemplates.Name | ||
DifferenceObject = $CustomTemplates | ||
ErrorAction = "SilentlyContinue" | ||
} | ||
if (($Null -eq $CustomTemplates) -or ($Null -ne (Compare-Object @params))) { | ||
Write-ToEventLog -Message "Error: Local templates in $CustomTemplates do not match $Uri." | ||
$Message += "`nLocal templates in $CustomTemplates do not match $Uri." | ||
$Result = 1 | ||
} | ||
|
||
# Check whether templates are registered | ||
$RegisteredTemplates = Get-UevTemplate -ErrorAction "SilentlyContinue" | ||
if ($Null -eq $RegisteredTemplates) { | ||
Write-ToEventLog -Message "Error: No settings templates are registered." | ||
$Message += "`nNo settings templates are registered." | ||
$Result = 1 | ||
} | ||
|
||
# Check whether a reboot is required | ||
if ($status.UevRebootRequired -eq $True) { | ||
Write-ToEventLog -Message "Warning: Reboot required to enable the UE-V service." | ||
$Message += "`nReboot required to enable the UE-V service." | ||
$Result = 1 | ||
} | ||
|
||
# Exit with an error | ||
if ($Result -eq 1) { | ||
Write-Host $Message | ||
exit $Result | ||
} | ||
else { | ||
# If we get here, all is good | ||
Write-ToEventLog -Message "UE-V service is enabled. $($RegisteredTemplates.Count) settings templates are registered." | ||
Write-Host "UE-V service is enabled. $($RegisteredTemplates.Count) settings templates are registered." | ||
exit 0 | ||
} | ||
} | ||
else { | ||
Write-ToEventLog -Message "Warning: UE-V service is not enabled." | ||
Write-Host "UE-V service is not enabled." | ||
exit 1 | ||
} | ||
} | ||
else { | ||
Write-ToEventLog -Message "Error: UEV module not installed." | ||
Write-Host "UEV module not installed." | ||
exit 1 | ||
} | ||
} | ||
else { | ||
Write-ToEventLog -Message "Warning: Windows 10/11 Enterprise is required to enable UE-V." | ||
Write-Host "Windows 10/11 Enterprise is required to enable UE-V." | ||
exit 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
function Get-AzureBlobItem { | ||
<# | ||
.SYNOPSIS | ||
Returns an array of items and properties from an Azure blog storage URL. | ||
.DESCRIPTION | ||
Queries an Azure blog storage URL and returns an array with properties of files in a Container. | ||
Requires Public access level of anonymous read access to the blob storage container. | ||
Works with PowerShell Core. | ||
.NOTES | ||
Author: Aaron Parker | ||
Twitter: @stealthpuppy | ||
.PARAMETER Url | ||
The Azure blob storage container URL. The container must be enabled for anonymous read access. | ||
The URL must include the List Container request URI. See https://docs.microsoft.com/en-us/rest/api/storageservices/list-containers2 for more information. | ||
.EXAMPLE | ||
Get-AzureBlobItems -Uri "https://aaronparker.blob.core.windows.net/folder/?comp=list" | ||
Description: | ||
Returns the list of files from the supplied URL, with Name, URL, Size and Last Modified properties for each item. | ||
#> | ||
[CmdletBinding(SupportsShouldProcess = $False)] | ||
[OutputType([System.Management.Automation.PSObject])] | ||
param ( | ||
[Parameter(ValueFromPipeline = $True, Mandatory = $True, HelpMessage = "Azure blob storage URL with List Containers request URI '?comp=list'.")] | ||
[ValidatePattern("^(http|https)://")] | ||
[System.String] $Uri | ||
) | ||
|
||
begin {} | ||
process { | ||
# Get response from Azure blog storage; Convert contents into usable XML, removing extraneous leading characters | ||
try { | ||
$iwrParams = @{ | ||
Uri = $Uri | ||
UseBasicParsing = $True | ||
ContentType = "application/xml" | ||
ErrorAction = "Stop" | ||
} | ||
$list = Invoke-WebRequest @iwrParams | ||
} | ||
catch [System.Net.WebException] { | ||
Write-Warning -Message ([System.String]::Format("Error : {0}", $_.Exception.Message)) | ||
throw $_.Exception.Message | ||
} | ||
catch [System.Exception] { | ||
Write-Warning -Message "failed to download: $Uri." | ||
throw $_.Exception.Message | ||
} | ||
if ($Null -ne $list) { | ||
[System.Xml.XmlDocument] $xml = $list.Content.Substring($list.Content.IndexOf("<?xml", 0)) | ||
|
||
# Build an object with file properties to return on the pipeline | ||
$fileList = New-Object -TypeName "System.Collections.ArrayList" | ||
foreach ($node in (Select-Xml -XPath "//Blobs/Blob" -Xml $xml).Node) { | ||
$PSObject = [PSCustomObject] @{ | ||
Name = $($node | Select-Object -ExpandProperty "Name") | ||
Uri = $($node | Select-Object -ExpandProperty "Url") | ||
Size = $($node | Select-Object -ExpandProperty "Size") | ||
LastModified = $($node | Select-Object -ExpandProperty "LastModified") | ||
} | ||
$fileList.Add($PSObject) | Out-Null | ||
} | ||
if ($Null -ne $fileList) { | ||
Write-Output -InputObject $fileList | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
trigger: | ||
branches: | ||
include: | ||
- main | ||
paths: | ||
include: | ||
- Uev/templates/* | ||
- Uev/tests/* | ||
- Uev/Publish-Templates.yml | ||
|
||
jobs: | ||
- job: push_templates | ||
pool: | ||
vmImage: windows-latest | ||
steps: | ||
- checkout: self | ||
persistCredentials: true | ||
|
||
- task: PowerShell@2 | ||
displayName: "Install Pester" | ||
inputs: | ||
targetType: 'inline' | ||
script: | | ||
Install-Module -Name "Pester" -Force -Confirm:$False | ||
verbosePreference: 'SilentlyContinue' | ||
pwsh: true | ||
workingDirectory: '$(Build.SourcesDirectory)' | ||
|
||
- task: PowerShell@2 | ||
displayName: "Validate templates against UE-V schema" | ||
inputs: | ||
targetType: 'inline' | ||
script: | | ||
Import-Module -Name "Pester" -Force | ||
$Config = [PesterConfiguration]::Default | ||
$Config.Run.Path = '$(Build.SourcesDirectory)\Uev\tests' | ||
$Config.Run.PassThru = $True | ||
$Config.CodeCoverage.Enabled = $False | ||
$Config.TestResult.Enabled = $True | ||
$Config.TestResult.OutputFormat = "NUnitXml" | ||
$Config.TestResult.OutputPath = ".\TestResults.xml" | ||
Invoke-Pester -Configuration $Config | ||
verbosePreference: 'SilentlyContinue' | ||
pwsh: true | ||
workingDirectory: '$(Build.SourcesDirectory)\Uev\tests' | ||
|
||
- task: AzureFileCopy@4 | ||
displayName: "Push templates to storage account" | ||
inputs: | ||
sourcePath: '$(Build.SourcesDirectory)\Uev\templates\*.xml' | ||
azureSubscription: 'Visual Studio Enterprise Subscription(63e8f660-f6a4-4ac5-ad4e-623268509f20)' | ||
destination: 'AzureBlob' | ||
storage: 'stpydeviceause' | ||
containerName: 'uev' | ||
additionalArgumentsForBlobCopy: '--log-level=INFO' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# User Experience Virtualization | ||
|
||
* `Remediate-Uev.ps1` - a Proactive Remediation that enables the UE-V client, and downloads and registers a set of templates from an Azure storage account | ||
* `Detect-Uev.ps1` - a Proactive Remediation to detect the status of the UE-V client | ||
* `./templates` - custom UE-V templates | ||
* `UserExperienceVirtualization-Profile.json` - an Intune Settings Catalog device configuration profile to configure User Experience Virtualization on Windows PCs | ||
|
||
## Tests | ||
|
||
[![Build Status](https://dev.azure.com/stealthpuppyLab/Uev/_apis/build/status/aaronparker.intune?branchName=main)](https://dev.azure.com/stealthpuppyLab/Uev/_build/latest?definitionId=15&branchName=main) | ||
|
||
* `Publish-Templates.yml` - an Azure Pipeline that validates UE-V templates and uploads the templates to blob storage on an Azure storage account | ||
* `./tests` - Pester tests to validate the UE-V templates | ||
|
||
## Other scripts | ||
|
||
* `Get-AzureBlobItem.ps1` - a function that returns items from Azure blob storage |
Oops, something went wrong.