Script to setup Windows 10 1903
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

704 lines
19 KiB

#requires -Version 2.0
$script:MachineExtensionGuids = '[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]'
$script:UserExtensionGuids = '[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F73-3407-48AE-BA88-E8213C6761F1}]'
function OpenPolicyFile
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $Path
)
$policyFile = New-Object TJX.PolFileEditor.PolFile
$policyFile.FileName = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)
if (Test-Path -LiteralPath $policyFile.FileName)
{
try
{
$policyFile.LoadFile()
}
catch [TJX.PolFileEditor.FileFormatException]
{
$message = "File '$Path' is not a valid POL file."
$exception = New-Object System.Exception($message)
$errorRecord = New-Object System.Management.Automation.ErrorRecord(
$exception, 'InvalidPolFileContents', [System.Management.Automation.ErrorCategory]::InvalidData, $Path
)
throw $errorRecord
}
catch
{
$errorRecord = $_
$message = "Error loading policy file at path '$Path': $($errorRecord.Exception.Message)"
$exception = New-Object System.Exception($message, $errorRecord.Exception)
$newErrorRecord = New-Object System.Management.Automation.ErrorRecord(
$exception, 'FailedToOpenPolicyFile', [System.Management.Automation.ErrorCategory]::OperationStopped, $Path
)
throw $newErrorRecord
}
}
return $policyFile
}
function PolEntryToPsObject
{
param (
[TJX.PolFileEditor.PolEntry] $PolEntry
)
$type = PolEntryTypeToRegistryValueKind $PolEntry.Type
$data = GetEntryData -Entry $PolEntry -Type $type
return New-Object psobject -Property @{
Key = $PolEntry.KeyName
ValueName = $PolEntry.ValueName
Type = $type
Data = $data
}
}
function GetEntryData
{
param (
[TJX.PolFileEditor.PolEntry] $Entry,
[Microsoft.Win32.RegistryValueKind] $Type
)
switch ($type)
{
([Microsoft.Win32.RegistryValueKind]::Binary)
{
return $Entry.BinaryValue
}
([Microsoft.Win32.RegistryValueKind]::DWord)
{
return $Entry.DWORDValue
}
([Microsoft.Win32.RegistryValueKind]::ExpandString)
{
return $Entry.StringValue
}
([Microsoft.Win32.RegistryValueKind]::MultiString)
{
return $Entry.MultiStringValue
}
([Microsoft.Win32.RegistryValueKind]::QWord)
{
return $Entry.QWORDValue
}
([Microsoft.Win32.RegistryValueKind]::String)
{
return $Entry.StringValue
}
}
}
function SavePolicyFile
{
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $true)]
[TJX.PolFileEditor.PolFile] $PolicyFile,
[switch] $UpdateGptIni
)
if ($PSCmdlet.ShouldProcess($PolicyFile.FileName, 'Save new settings'))
{
$parentPath = Split-Path $PolicyFile.FileName -Parent
if (-not (Test-Path -LiteralPath $parentPath -PathType Container))
{
try
{
$null = New-Item -Path $parentPath -ItemType Directory -ErrorAction Stop -Confirm:$false -WhatIf:$false
}
catch
{
$errorRecord = $_
$message = "Error creating parent folder of path '$Path': $($errorRecord.Exception.Message)"
$exception = New-Object System.Exception($message, $errorRecord.Exception)
$newErrorRecord = New-Object System.Management.Automation.ErrorRecord(
$exception, 'CreateParentFolderError', $errorRecord.CategoryInfo.Category, $Path
)
throw $newErrorRecord
}
}
try
{
$PolicyFile.SaveFile()
}
catch
{
$errorRecord = $_
$message = "Error saving policy file to path '$($PolicyFile.FileName)': $($errorRecord.Exception.Message)"
$exception = New-Object System.Exception($message, $errorRecord.Exception)
$newErrorRecord = New-Object System.Management.Automation.ErrorRecord(
$exception, 'FailedToSavePolicyFile', [System.Management.Automation.ErrorCategory]::OperationStopped, $PolicyFile
)
throw $newErrorRecord
}
}
if ($UpdateGptIni)
{
if ($policyFile.FileName -match '^(.*)\\+([^\\]+)\\+[^\\]+$' -and
$Matches[2] -eq 'User' -or $Matches[2] -eq 'Machine')
{
$iniPath = Join-Path $Matches[1] GPT.ini
if (Test-Path -LiteralPath $iniPath -PathType Leaf)
{
if ($PSCmdlet.ShouldProcess($iniPath, 'Increment version number in INI file'))
{
IncrementGptIniVersion -Path $iniPath -PolicyType $Matches[2] -Confirm:$false -WhatIf:$false
}
}
else
{
if ($PSCmdlet.ShouldProcess($iniPath, 'Create new gpt.ini file'))
{
NewGptIni -Path $iniPath -PolicyType $Matches[2]
}
}
}
}
}
function NewGptIni
{
param (
[string] $Path,
[string[]] $PolicyType
)
$parent = Split-Path $Path -Parent
if (-not (Test-Path $parent -PathType Container))
{
$null = New-Item -Path $parent -ItemType Directory -ErrorAction Stop
}
$version = GetNewVersionNumber -Version 0 -PolicyType $PolicyType
Set-Content -Path $Path -Encoding Ascii -Value @"
[General]
gPCMachineExtensionNames=$script:MachineExtensionGuids
Version=$version
gPCUserExtensionNames=$script:UserExtensionGuids
"@
}
function IncrementGptIniVersion
{
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[string] $Path,
[string[]] $PolicyType
)
$foundVersionLine = $false
$section = ''
$newContents = @(
foreach ($line in Get-Content $Path)
{
# This might not be the most unreadable regex ever, but it's trying hard to be!
# It's looking for section lines: [SectionName]
if ($line -match '^\s*\[([^\]]+)\]\s*$')
{
if ($section -eq 'General')
{
if (-not $foundVersionLine)
{
$foundVersionLine = $true
$newVersion = GetNewVersionNumber -Version 0 -PolicyType $PolicyType
"Version=$newVersion"
}
if (-not $foundMachineExtensionLine)
{
$foundMachineExtensionLine = $true
"gPCMachineExtensionNames=$script:MachineExtensionGuids"
}
if (-not $foundUserExtensionLine)
{
$foundUserExtensionLine = $true
"gPCUserExtensionNames=$script:UserExtensionGuids"
}
}
$section = $matches[1]
}
elseif ($section -eq 'General' -and
$line -match '^\s*Version\s*=\s*(\d+)\s*$' -and
$null -ne ($version = $matches[1] -as [uint32]))
{
$foundVersionLine = $true
$newVersion = GetNewVersionNumber -Version $version -PolicyType $PolicyType
$line = "Version=$newVersion"
}
elseif ($section -eq 'General' -and $line -match '^\s*gPC(Machine|User)ExtensionNames\s*=')
{
if ($matches[1] -eq 'Machine')
{
$foundMachineExtensionLine = $true
}
else
{
$foundUserExtensionLine = $true
}
$line = EnsureAdminTemplateCseGuidsArePresent $line
}
$line
}
if ($section -eq 'General')
{
if (-not $foundVersionLine)
{
$foundVersionLine = $true
$newVersion = GetNewVersionNumber -Version 0 -PolicyType $PolicyType
"Version=$newVersion"
}
if (-not $foundMachineExtensionLine)
{
$foundMachineExtensionLine = $true
"gPCMachineExtensionNames=$script:MachineExtensionGuids"
}
if (-not $foundUserExtensionLine)
{
$foundUserExtensionLine = $true
"gPCUserExtensionNames=$script:MachineExtensionGuids"
}
}
)
if ($PSCmdlet.ShouldProcess($Path, 'Increment Version number'))
{
Set-Content -Path $Path -Value $newContents -Encoding Ascii -Confirm:$false -WhatIf:$false
}
}
function EnsureAdminTemplateCseGuidsArePresent
{
param ([string] $Line)
# These lines contain pairs of GUIDs in "registry" format (with the curly braces), separated by nothing, with
# each pair of GUIDs wrapped in square brackets. Example:
# gPCMachineExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]
# Per Darren Mar-Elia, these GUIDs must be in alphabetical order, or GP processing will have problems.
if ($Line -notmatch '\s*(gPC(?:Machine|User)ExtensionNames)\s*=\s*(.*)$')
{
throw "Malformed gpt.ini line: $Line"
}
$valueName = $matches[1]
$guidStrings = @($matches[2] -split '(?<=\])(?=\[)')
if ($matches[1] -eq 'gPCMachineExtensionNames')
{
$toolExtensionGuid = $script:MachineExtensionGuids
}
else
{
$toolExtensionGuid = $script:UserExtensionGuids
}
$guidList = @(
$guidStrings
$toolExtensionGuid
)
$newGuidString = ($guidList | Sort-Object -Unique) -join ''
return "$valueName=$newGuidString"
}
function GetNewVersionNumber
{
param (
[UInt32] $Version,
[string[]] $PolicyType
)
# User version is the high 16 bits, Machine version is the low 16 bits.
# Reference: http://blogs.technet.com/b/grouppolicy/archive/2007/12/14/understanding-the-gpo-version-number.aspx
$pair = UInt32ToUInt16Pair -UInt32 $version
if ($PolicyType -contains 'User')
{
$pair.HighPart++
}
if ($PolicyType -contains 'Machine')
{
$pair.LowPart++
}
return UInt16PairToUInt32 -UInt16Pair $pair
}
function UInt32ToUInt16Pair
{
param ([UInt32] $UInt32)
# Deliberately avoiding bitwise shift operators here, for PowerShell v2 compatibility.
$lowPart = $UInt32 -band 0xFFFF
$highPart = ($UInt32 - $lowPart) / 0x10000
return New-Object psobject -Property @{
LowPart = [UInt16] $lowPart
HighPart = [UInt16] $highPart
}
}
function UInt16PairToUInt32
{
param ([object] $UInt16Pair)
# Deliberately avoiding bitwise shift operators here, for PowerShell v2 compatibility.
return ([UInt32] $UInt16Pair.HighPart) * 0x10000 + $UInt16Pair.LowPart
}
function PolEntryTypeToRegistryValueKind
{
param ([TJX.PolFileEditor.PolEntryType] $PolEntryType)
switch ($PolEntryType)
{
([TJX.PolFileEditor.PolEntryType]::REG_NONE)
{
return [Microsoft.Win32.RegistryValueKind]::None
}
([TJX.PolFileEditor.PolEntryType]::REG_DWORD)
{
return [Microsoft.Win32.RegistryValueKind]::DWord
}
([TJX.PolFileEditor.PolEntryType]::REG_DWORD_BIG_ENDIAN)
{
return [Microsoft.Win32.RegistryValueKind]::DWord
}
([TJX.PolFileEditor.PolEntryType]::REG_BINARY)
{
return [Microsoft.Win32.RegistryValueKind]::Binary
}
([TJX.PolFileEditor.PolEntryType]::REG_EXPAND_SZ)
{
return [Microsoft.Win32.RegistryValueKind]::ExpandString
}
([TJX.PolFileEditor.PolEntryType]::REG_MULTI_SZ)
{
return [Microsoft.Win32.RegistryValueKind]::MultiString
}
([TJX.PolFileEditor.PolEntryType]::REG_QWORD)
{
return [Microsoft.Win32.RegistryValueKind]::QWord
}
([TJX.PolFileEditor.PolEntryType]::REG_SZ)
{
return [Microsoft.Win32.RegistryValueKind]::String
}
}
}
function GetPolFilePath
{
param (
[Parameter(Mandatory = $true, ParameterSetName = 'PolicyType')]
[string] $PolicyType,
[Parameter(Mandatory = $true, ParameterSetName = 'Account')]
[string] $Account
)
if ($PolicyType)
{
switch ($PolicyType)
{
'Machine'
{
return Join-Path $env:SystemRoot System32\GroupPolicy\Machine\registry.pol
}
'User'
{
return Join-Path $env:SystemRoot System32\GroupPolicy\User\registry.pol
}
'Administrators'
{
# BUILTIN\Administrators well-known SID
return Join-Path $env:SystemRoot System32\GroupPolicyUsers\S-1-5-32-544\User\registry.pol
}
'NonAdministrators'
{
# BUILTIN\Users well-known SID
return Join-Path $env:SystemRoot System32\GroupPolicyUsers\S-1-5-32-545\User\registry.pol
}
}
}
else
{
try
{
$sid = $Account -as [System.Security.Principal.SecurityIdentifier]
if ($null -eq $sid)
{
$sid = GetSidForAccount $Account
}
return Join-Path $env:SystemRoot "System32\GroupPolicyUsers\$($sid.Value)\User\registry.pol"
}
catch
{
throw
}
}
}
function GetSidForAccount($Account)
{
$acc = $Account
if ($acc -notlike '*\*') { $acc = "$env:COMPUTERNAME\$acc" }
try
{
$ntAccount = [System.Security.Principal.NTAccount]$acc
return $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
}
catch
{
$message = "Could not translate account '$acc' to a security identifier."
$exception = New-Object System.Exception($message, $_.Exception)
$errorRecord = New-Object System.Management.Automation.ErrorRecord(
$exception,
'CouldNotGetSidForAccount',
[System.Management.Automation.ErrorCategory]::ObjectNotFound,
$Acc
)
throw $errorRecord
}
}
function DataIsEqual
{
param (
[object] $First,
[object] $Second,
[Microsoft.Win32.RegistryValueKind] $Type
)
if ($Type -eq [Microsoft.Win32.RegistryValueKind]::String -or
$Type -eq [Microsoft.Win32.RegistryValueKind]::ExpandString -or
$Type -eq [Microsoft.Win32.RegistryValueKind]::DWord -or
$Type -eq [Microsoft.Win32.RegistryValueKind]::QWord)
{
return @($First)[0] -ceq @($Second)[0]
}
# If we get here, $Type is either MultiString or Binary, both of which need to compare arrays.
# The PolicyFileEditor module never returns type Unknown or None.
$First = @($First)
$Second = @($Second)
if ($First.Count -ne $Second.Count) { return $false }
$count = $First.Count
for ($i = 0; $i -lt $count; $i++)
{
if ($First[$i] -cne $Second[$i]) { return $false }
}
return $true
}
function ParseKeyValueName
{
param ([string] $KeyValueName)
$key = $KeyValueName -replace '^\\+|\\+$'
$valueName = ''
if ($KeyValueName -match '^\\*(?<Key>.+?)\\+(?<ValueName>[^\\]*)$')
{
$key = $matches['Key'] -replace '\\{2,}', '\'
$valueName = $matches['ValueName']
}
return $key, $valueName
}
function GetTargetResourceCommon
{
param (
[string] $Path,
[string] $KeyValueName
)
$configuration = @{
KeyValueName = $KeyValueName
Ensure = 'Absent'
Data = $null
Type = [Microsoft.Win32.RegistryValueKind]::Unknown
}
if (Test-Path -LiteralPath $path -PathType Leaf)
{
$key, $valueName = ParseKeyValueName $KeyValueName
$entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName
if ($entry)
{
$configuration['Ensure'] = 'Present'
$configuration['Type'] = $entry.Type
$configuration['Data'] = @($entry.Data)
}
}
return $configuration
}
function SetTargetResourceCommon
{
param (
[string] $Path,
[string] $KeyValueName,
[string] $Ensure,
[string[]] $Data,
[Microsoft.Win32.RegistryValueKind] $Type
)
if ($null -eq $Data) { $Data = @() }
try
{
Assert-ValidDataAndType -Data $Data -Type $Type
}
catch
{
Write-Error -ErrorRecord $_
return
}
$key, $valueName = ParseKeyValueName $KeyValueName
if ($Ensure -eq 'Present')
{
Set-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName -Data $Data -Type $Type
}
else
{
Remove-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName
}
}
function TestTargetResourceCommon
{
[OutputType([bool])]
param (
[string] $Path,
[string] $KeyValueName,
[string] $Ensure,
[string[]] $Data,
[Microsoft.Win32.RegistryValueKind] $Type
)
if ($null -eq $Data) { $Data = @() }
try
{
Assert-ValidDataAndType -Data $Data -Type $Type
}
catch
{
Write-Error -ErrorRecord $_
return $false
}
$key, $valueName = ParseKeyValueName $KeyValueName
$fileExists = Test-Path -LiteralPath $Path -PathType Leaf
if ($Ensure -eq 'Present')
{
if (-not $fileExists) { return $false }
$entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName
return $null -ne $entry -and $Type -eq $entry.Type -and (DataIsEqual $entry.Data $Data -Type $Type)
}
else # Ensure is 'Absent'
{
if (-not $fileExists) { return $true }
$entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName
return $null -eq $entry
}
}
function Assert-ValidDataAndType
{
param (
[string[]] $Data,
[Microsoft.Win32.RegistryValueKind] $Type
)
if ($Type -ne [Microsoft.Win32.RegistryValueKind]::MultiString -and
$Type -ne [Microsoft.Win32.RegistryValueKind]::Binary -and
$Data.Count -gt 1)
{
$errorRecord = InvalidDataTypeCombinationErrorRecord -Message 'Do not pass arrays with multiple values to the -Data parameter when -Type is not set to either Binary or MultiString.'
throw $errorRecord
}
}
function InvalidDataTypeCombinationErrorRecord($Message)
{
$exception = New-Object System.Exception($Message)
return New-Object System.Management.Automation.ErrorRecord(
$exception, 'InvalidDataTypeCombination', [System.Management.Automation.ErrorCategory]::InvalidArgument, $null
)
}