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 {

    }
}