private/createOrSetGPO.ps1
function CreateOrSetGPO { [CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName='Default')] Param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'SpecificOrg')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'SpecificComponent')] [ValidateScript({ [bool](get-rbacOrg -org $_)})] [ArgumentCompleter( { param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) (get-rbacOrg -org "$wordToComplete*").Org })] [String]$Org, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'SpecificComponent')] [ValidateScript({ $(get-rbacComponent).component.contains($_) })] [ArgumentCompleter( { param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) if ($fakeBoundParameters.containsKey('Org')) { (get-rbacComponent -org $fakeBoundParameters.Org -component "$wordToComplete*" | sort-object -unique Component).Component } else { (get-rbacComponent -component "$wordToComplete*").Component } })] [String]$Component, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Global')] [switch]$Global, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [Hashtable]$GPOTemplate, [Microsoft.ActiveDirectory.Management.ADDirectoryServer]$Server = (get-addomainController -Writable -Discover) ) Begin{ $Domain = get-ADDomain $DomainDNSRoot= $Domain.DNSRoot $DomainGPOPath="\\$DomainDNSRoot\sysvol\$DomainDNSRoot\Policies" $shouldProcess = @{ Confirm = [bool]($ConfirmPreference -eq "low") Whatif = [bool]($WhatIfPreference.IsPresent) verbose = [bool]($verbosePreference -eq "continue") } $GPOHeader_SecEdit_INF=@' [Unicode] Unicode=yes [Version] signature="$CHICAGO$" Revision=1 '@ $GPOHeader_GPT_INI_HEADER=@' [General] Version=1 displayName=New Group Policy Object '@ } PROCESS { write-loghandler -level "Verbose" -message "Creating GPOs (USING DC: $($Server.Hostname))" if ($component) { $element = get-rbacComponent -org $org -component $Component -detailed } elseif ($org) { $element = get-rbacOrg -org $org -detailed } else { $element = get-rbacGlobal } if (-not $element) { throw "Invalid RBAC Element (maybe wrong org for component?)" } $GPOName = "{0}-{1}" -f $GPOTemplate.Metadata.NamePrefix, $Element.objectMidName write-loghandler -level "Info" -message "Processing GPO: $GPOName" -target $GPOName $GPOObject = if ($PSCmdlet.ShouldProcess($GPOName, "Create / get GPO")) { try { new-gpo -name $GPOName -erroraction stop @shouldProcess -server $Server $preExisting = $false write-loghandler -level "Verbose" -message "Created GPO" -target $GPOName } catch [System.Management.Automation.RemoteException]{ if ($_.exception.serializedRemoteException.message -match "The command cannot be completed because a .*GPO already exists in the .* domain") { write-loghandler -level "Verbose" -message "GPO $GPOName already exists (error: $($_.exception.getType().fullname))" $preExisting = $true get-gpo $GPOName -server $Server } else { throw $_ } } catch { write-loghandler -level "warning" -message $_.exception.getType().fullname throw $_ } } else { get-gpo $GPOName -server $Server } $GPOBaseFilePath = "{0}\{{{1}}}" -f $DomainGPOPath, $GPOObject.ID.GUID #region set GPLink if ($PSCmdlet.ShouldProcess($GPOName, "Create or Set GPLink")) { try { $GPOLinkPath = if ($element.type -eq 'Global') { $settings.OUPaths.TenantRoot } else { $Element.DistinguishedName } new-gplink -name $GPOName -target $GPOLinkPath -erroraction stop @shouldProcess -server $Server| out-null write-loghandler -level "Info" -message " --> Created GPLink" } catch [System.Management.Automation.RemoteException]{ if ($($_.exception.serializedRemoteException.message) -match "The GPO named .* is already linked to a Scope.*") { write-loghandler -level "Verbose" -message "GPLink for $GPOName already exists (error: $($_.exception.getType().fullname))" } elseif ($_.exception.serializedRemoteException.message -match "There is no such object on the server.*") { write-loghandler -level "warning" -message "GPLink could not be created, AD is probably replicating. Please try again later." } else { $_.exception.errorrecord throw $_ } } catch { write-warning $_.exception.getType().fullname throw $_ } $GPLinkParams = @{ target = $GPOLinkPath Order = $GPOTemplate.Metadata.LinkOrder LinkEnabled = "Yes" Enforced = "No" } $GPOObject | set-gplink @GPLinkParams -server $Server | out-null write-loghandler -level "Info" -message ("Set GPLink: {0,-32} : #{1,-1} ; Enforced: {2,-5} ; Enabled: {3,-5} ; Path: {4}" -f $GPOObject.displayName, $GPLinkParams.Order, $GPLinkParams.Enforced, $GPLinkParams.LinkEnabled, $GPLinkParams.target) } #endregion #region Set permissions on GPO write-loghandler -level "Debug" -message "Beginning to process GPO Permissions" $GPPermissions = [hashtable]::new() $GPPermissions.GPOEditDeleteModifySecurity = @() $GlobalGPOEdit = resolveEntityReferences -rbacElement $(get-rbacglobal) -RightsAndPrincipals @{rights="GPOEdit"} foreach ($GPPermissionLevel in $GPOTemplate.metadata.gppermissions.getEnumerator()) { $entityList = @(resolveEntityReferences -rbacElement $element -rightsAndPrincipals $GPPermissionLevel.value -includeParents) $GPPermissions.$($GPPermissionLevel.key) = $EntityList } if ($GlobalGPOEdit.SID -notIn ($GPPermissions.GPOEditDeleteModifySecurity).SID) { write-loghandler -level "Verbose" -message "Adding Global GPOEdit with GPOEditDeleteModifySecurity as it was not present." -target $GPOName $GPPermissions.GPOEditDeleteModifySecurity += $GlobalGPOEdit } write-loghandler -level "Info" -message "Finished gathering permissions, applying them..." -target $GPOName foreach ($PermissionLevel in $GPPermissions.GetEnumerator()) { foreach ($entity in $PermissionLevel.value) { if ($PSCmdlet.ShouldProcess($GPOName, ("Set permissions: {0,-10}; on entityType: {1,-10}; entity: {2}" -f $permissionLevel.key, $entity.objectClass, $entity.name))) { try { $Params = @{ name = $GPOName TargetName = $entity.name TargetType = $entity.objectClass Permissionlevel = $permissionLevel.key Replace = $true } set-GPPermission @Params -ErrorAction stop -server $Server | out-null } Catch { $_.Exception.getType().FullName $_ | format-list * -force write-loghandler -level "warning" -message "Error setting permissions for $GroupName on $GPOName" throw $_ } } } } #endregion if (-not $preExisting -or $GPOTemplate.Metadata.AlwaysRebuild) { write-loghandler -level "Info" -message " ---> Processing settings" -target $GPOName $GPCExtensionList = "[{827D319E-6EAC-11D2-A4EA-00C04F79F83A}{803E14A0-B4FB-11D0-A0D0-00A0C90F574B}]" #region SecEdit.inf $SecEditPath ="{0}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf" -f $GPOBaseFilePath $SecEditContents = try { $GPOHeader_SecEdit_INF if ($GPOTemplate.SecEdit) { foreach ($Section in $GPOTemplate.secedit.getEnumerator()){ $SectionString = "[{0}]" -f $section.key $sectionString write-loghandler -level "Verbose" -message "section: $($section.key)" -indentlevel 2 try { foreach ($setting in $Section.value.getEnumerator()) { $settingTable = [hashtable]::new() #Special Handling for Group membership Switch ($section.key) { "Group Membership" { # Group Membership lines must obey the following rules: # principal__Members = principals # principal__Memberof = principals # CASING and UNDERSCORES ARE SIGNIFICANT ON the suffix. It MUST be 2x _, then uppercase M, then lowercase # SIDS must be prefixed with a * # Any __Memberof line MUST have a matching __Members line # If Members is left blank, it will clear membership. # If Memberof is blank, it will not change membership. if ($setting.Value.resolveKeyName) { write-loghandler -level "Verbose" -message "Resolving Key Name" # We don't need includeparents here because they're pulled in via the "memberof" permission $GPOPrefix = (resolveEntityReferences -rbacElement $element -rightsAndPrincipals @{Rights = $Setting.key} ).GPORef } else { $GPOPrefix = $setting.key } write-loghandler -level "Verbose" -message "Using Key name: $GPOPrefix" if ($setting.value.containsKey("Members")) { write-Host "GPO: adding Group membership for $($Setting.key)__Members" #casing and underscores are significant! $settingTable."$($GPOPrefix)__Members" = $setting.Value.members } if ($setting.value.containsKey("MemberOf")) { write-Host "GPO: adding Group membership for $($Setting.key)__Memberof" #casing and underscores are significant! $settingTable."$($GPOPrefix)__Memberof" = $setting.Value.Memberof if (-not $settingTable."$($GPOPrefix)__Members") { write-loghandler -level "Verbose" -message "No 'members' specified for $GPOPrefix. This would normally break GPOs. Creating blank entry." $settingTable."$($GPOPrefix)__Members" = "" } } write-loghandler -level "Verbose" -message "Done resolving key name..." } default { $settingTable."$($setting.key)" = $setting.value } } write-loghandler -level "Verbose" -message "Setting Secedit \ $($section.key)" foreach ($entry in $settingTable.GetEnumerator()) { try { if ($null -ne $entry.value -and $entry.value -ne "" ) { $entityList = resolveEntityReferences -rbacElement $element -rightsAndPrincipals $entry.value -includeParents $EntityString = $entityList.GPORef.where({ -not [string]::IsNullOrEmpty($_)}) -join "," } else { $entityString = "" } "{0} ={1}" -f $entry.key, $entityString } catch { write-warning $_.exception.getType().fullname write-loghandler -level "warning" -message "Something went wrong during GPO settingTable enumeration" write-error $_ } } } } catch [System.Management.Automation.RuntimeException] { write-loghandler -level "warning" -message "Missing settings or bad format in GPO template (Section: secedit \ $($section.key))..." $_ | format-list * -force } catch { write-warning $_.exception.getType().fullname write-loghandler -level "warning" -message "Something bad happened" $_ | format-list * -force } } } } catch [System.Management.Automation.RuntimeException] { write-loghandler -level "warning" -message "Missing settings or bad format in GPO template under SECEDIT..." $_ | format-list * -force } if ($PSCmdlet.ShouldProcess($SecEditPath, ("Writing out SecEdit.inf"))) { new-item $SecEditPath -force | out-null $SecEditContents | out-file -append $SecEditPath -Encoding unicode } else { $secEditContents } #endRegion #region RegPol if ($GPOTemplate.RegPol) { $GPCExtensionList = "[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]" + $GPCExtensionList $RegPolPath ="{0}\Machine\Registry.pol" -f $GPOBaseFilePath $PolicyDefs = foreach ($policyItem in $GPOTemplate.RegPol) { $Value = if ($PolicyItem.ValueCollection) { $entityList = resolveEntityReferences -rbacElement $element -rightsAndPrincipals $PolicyItem.ValueCollection # Special Handling switch ($policyItem.ValueName) { "ADPasswordEncryptionPrincipal" { @($($entityList.NetBIOS.where({ -not [string]::IsNullOrEmpty($_)})))[0] } default { $entityList.StdRef.where({ -not [string]::IsNullOrEmpty($_)}) -join "," } } } else { $policyItem.valueData } $Policy = @{ keyName = $policyItem.keyName valueName = $policyItem.valueName ValueType = $Policyitem.valueType ValueData = $value } write-host ("GPO: Setting policy {2,-10}- {0}\{1} = {3}" -f $policy.keyName, $Policy.valueName, $policy.valueType, $policy.ValueData) New-GPRegistryPolicy @policy } Create-GPRegistryPolicyFile -path $RegPolPath append-RegistryPolicies -registryPolicies $PolicyDefs -path $RegPolPath } # endregion # region GPPrefRegistryValues if ($GPOTemplate.GPPrefRegistryValues) { $GPCExtensionList = $GPCExtensionList + "[{B087BE9D-ED37-454F-AF9C-04291E351182}{BEE07A6A-EC9F-4659-B8C9-0B1937907C83}]" foreach ($RegistryPref in $GPOTemplate.GPPrefRegistryValues) { set-GPPrefRegistryValue -GUID $GPOObject.ID @RegistryPref -verbose } } # endregion if ($PSCmdlet.ShouldProcess($GPOObject.Path,"Set GPCMachineExtensions on GPO and increment version")) { try { $GPOOldVersionAD = (get-adobject -identity $GPOObject.Path -properties versionNumber -server $Server).versionNumber } catch { write-loghandler -level "warning" -message "Couldn't find GPOObject, defaulting to old version = 0 ( path: $($GPOObject.Path))" $GPOOldVersionAD = 0 } try { $GPTContent = get-content ("$GPOBaseFilePath\GPT.ini") if (($GPTContent | select-string "Version=") -match "Version=([0-9]+)") { $matches[1] } else { write-loghandler -level "warning" -message "Couldn't find Version line in $GPOBaseFilePath\GPT.ini...." throw [System.Management.Automation.ItemNotFoundException] } }catch [System.Management.Automation.ItemNotFoundException] { write-loghandler -level "warning" -message "Couldn't find a file with a valid version number, defaulting to old version = 0 (path: $GPOBaseFilePath\GPT.ini)" $GPOOldVersionFile = 0 } catch { Write-warning $_.Exception.getType().FullName throw $_ } $newVersion = [int]([Math]::Max([int]$GPOOldVersionAD, [int]$GPOOldVersionFile) + 1) write-loghandler -level "Verbose" -message "GPO Versions: AD: $GPOOldVersionAD; File: $GPOOldVersionFile; New: $NewVersion" $gposetsuccess = $false $timeout = 10 for ($s = 0; $s -lt $timeout/$Settings.AppSettings.SleepLength; $s+= $Settings.AppSettings.SleepLength) { write-progress -activity "waiting for GPO creation" -SecondsRemaining $($timeout-($s * $Settings.AppSettings.SleepLength)) try { set-adobject -identity $GPOObject.Path -server $Server -replace @{gPCMachineExtensionNames = $GPCExtensionList; versionNumber = $newVersion} | out-null $gposetsuccess = $true continue } catch { start-sleep -Seconds $Settings.AppSettings.SleepLength } } write-progress -activity "waiting for GPO creation" -Completed if (-not $gposetsuccess) { write-error "Setting gPCMachineExtensionNames and version failed" write-loghandler -level "warning" -message "Timed out waiting for GPO object creation at $($GPOObject.Path)." write-loghandler -level "warning" -message "GPMC may act strangely until this is fixed. please re-run the script once replication is finished" } $GPT_Ini_Contents=@" [General] Version=$NewVersion displayName=$GPOName "@ # Apparently encoding unicode is bad news for gpt.ini $GPT_Ini_Contents | out-file $GPOBaseFilePath\gpt.ini -Force } } } End { } } |