Public/New-DelegateAdGpo.ps1
function New-DelegateAdGpo { <# .Synopsis Creates and Links new GPO with delegated permissions. .DESCRIPTION Create new custom delegated GPO, Delegate rights to an existing group and links it to the given OU.Key features: - Creates new GPO or modifies existing one - Delegates permissions to specified security group - Links GPO to target OU - Optionally imports settings from GPO backup - Disables user or computer settings based on scope - Supports idempotent operations - Implements security best practices .PARAMETER gpoDescription [String] Description of the GPO. Used to build the name. Only Characters a-z A-Z. The final GPO name will be constructed as: [Scope]-[Description] .PARAMETER gpoScope [ValidateSet] Scope of the GPO: - U: User settings (disables computer configuration) - C: Computer settings (disables user configuration) .PARAMETER gpoLinkPath [String] Distinguished Name of the OU where the GPO will be linked. Must be a valid AD path (CN=,DC=) .PARAMETER GpoAdmin [String] Security group that will be delegated GPO edit rights. Group must exist in AD. Permissions granted: GpoEditDeleteModifySecurity .PARAMETER gpoBackupID [String] GUID of the GPO backup to import settings from. Only used when restoring from backup. .PARAMETER gpoBackupPath [String] File system path containing the GPO backup. Must be accessible and contain valid backup. .EXAMPLE New-DelegateAdGpo -gpoDescription MyNewGPO -gpoScope C -gpoLinkPath "OU=Servers,OU=eguibarit,OU=local" -GpoAdmin "SL_GpoRight" .EXAMPLE New-DelegateAdGpo -gpoDescription MyNewGPO -gpoScope C -gpoLinkPath "OU=Servers,OU=eguibarit,OU=local" -GpoAdmin "SL_GpoRight" -gpoBackupID '1D872D71-D961-4FCE-87E0-1CD368B5616F' -gpoBackupPath 'C:\PsScripts\Backups' .EXAMPLE $Splat = @{ gpoDescription = 'MyNewGPO' gpoScope = 'C' gpoLinkPath = 'OU=Servers,OU=eguibarit,OU=local' GpoAdmin = 'SL_GpoRight' gpoBackupID = '1D872D71-D961-4FCE-87E0-1CD368B5616F' gpoBackupPath = 'C:\PsScripts\Backups' } New-DelegateAdGpo @Splat .OUTPUTS [Microsoft.GroupPolicy.Gpo] Returns the created or modified GPO object. .NOTES Used Functions: Name ║ Module/Namespace ══════════════════════════════════════╬══════════════════════════════ Get-FunctionDisplay ║ EguibarIT Get-AdObjectType ║ EguibarIT Test-IsValidDN ║ EguibarIT Get-ADDomainController ║ ActiveDirectory Get-GPO ║ GroupPolicy Import-GPO ║ GroupPolicy New-GPO ║ GroupPolicy New-GPLink ║ GroupPolicy Set-GPPermissions ║ GroupPolicy .NOTES Version: 1.3 DateModified: 31/Mar/2024 LasModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT .LINK https://learn.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/class-library/gppermissiontype-enumeration-microsoft-grouppolicy #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'DelegatedAdGpo' )] [OutputType([Object])] Param ( # Param1 GPO description, used to generate name [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Description of the GPO. Used to build the name (letters and numbers only).', Position = 0)] [ValidateNotNullOrEmpty()] [string] $gpoDescription, # Param2 GPO scope. U = Users, C = Computers [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Scope of the GPO. U for Users and C for Computers DEFAULT is U. The non-used part of the GPO will get disabled', Position = 1)] [ValidateSet('U', 'C', ignorecase = $false)] [string] $gpoScope, # Param3 GPO Link to OU [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'DistinguishedName where to link the newly created GPO', Position = 2)] [ValidateNotNullOrEmpty()] [ValidateScript( { Test-IsValidDN -ObjectDN $_ }, ErrorMessage = 'DistinguishedName provided is not valid! Please Check.' )] [Alias('DN', 'DistinguishedName', 'LDAPpath')] [string] $gpoLinkPath, # Param4 Domain Local Group with GPO Rights to be assigned [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Domain Local Group with GPO Rights to be assigned', Position = 3)] [ValidateNotNullOrEmpty()] $GpoAdmin, # Param5 Restore GPO settings from backup using the BackupID GUID [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Restore GPO settings from backup using the BackupID GUID', ParameterSetName = 'DelegatedAdGpo', Position = 4)] [Parameter(ParameterSetName = 'GpoBackup', Position = 4)] [Alias('BackupID')] [string] $gpoBackupID, # Param6 Path where Backups are stored [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Path where Backups are stored', ParameterSetName = 'GpoBackup', Position = 5)] [ValidateScript( { Test-Path $_ }, ErrorMessage = 'Backup path does not exist or is not accessible.')] [string] $gpoBackupPath ) Begin { Set-StrictMode -Version Latest # Get the GroupPolicy functionality through module import instead of direct assembly loading # This ensures the correct paths are used regardless of environment if ($null -ne $Variables -and $null -ne $Variables.Header) { $txt = ($Variables.Header -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end If ############################## # Module imports Import-MyModule -Name 'ActiveDirectory' -Verbose:$false Import-Module -Name 'GroupPolicy' -SkipEditionCheck -Verbose:$false ############################## # Variables Definition [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase) #$gpoAlreadyExist = [Microsoft.GroupPolicy.GroupPolicyObject]::New() $gpoName = '{0}-{1}' -f $PSBoundParameters['gpoScope'], $PSBoundParameters['gpoDescription'] $GpoAdmin = Get-ADObjectType -Identity $GpoAdmin try { [system.string]$dcServer = (Get-ADDomainController -Discover -Service 'PrimaryDC').HostName } catch { Write-Warning -Message 'Unable to locate primary domain controller' } #end Try-Catch } # End Begin Section Process { # Check if the GPO already exist $gpoAlreadyExist = Get-GPO -Name $gpoName -ErrorAction SilentlyContinue Write-Debug -Message ('Checking for existing GPO: {0}' -f $gpoName) if (-not $gpoAlreadyExist) { Write-Verbose -Message ('Policy: Create policy object {0}' -f $gpoName) $Splat = @{ Name = $gpoName Comment = $gpoName Server = $dcServer } if ($PSCmdlet.ShouldProcess("Creating GPO '$gpoName'", 'Confirm creation?')) { $gpoAlreadyExist = New-GPO @Splat Start-Sleep -Seconds 1 } #end If # https://learn.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/class-library/gppermissiontype-enumeration-microsoft-grouppolicy # Give Rights to SL_GpoAdminRight Write-Debug -Message ('Add GpoAdminRight to {0}' -f $gpoAlreadyExist.DisplayName) $Splat = @{ GUID = $gpoAlreadyExist.Id PermissionLevel = 'GpoEditDeleteModifySecurity' TargetName = $GpoAdmin.SamAccountName TargetType = 'group' Server = $dcServer } if ($PSCmdlet.ShouldProcess("Giving permissions to GPO '$gpoName'", 'Confirm giving permissions?')) { Set-GPPermissions @Splat } #end If # Disable the corresponding Settings section of the GPO If ($gpoScope -eq 'C') { if ($PSCmdlet.ShouldProcess("Disabling Users section on GPO '$gpoName'", 'Confirm disabling user section?')) { Write-Debug -Message ('Disable Policy User Settings on GPO {0}' -f $gpoAlreadyExist.DisplayName) $gpoAlreadyExist.GpoStatus = 'UserSettingsDisabled' } #end If } else { if ($PSCmdlet.ShouldProcess("Disabling Computers section on GPO '$gpoName'", 'Confirm disabling computer section?')) { Write-Debug -Message ('Disable Policy Computer Settings on GPO {0}' -f $gpoAlreadyExist.DisplayName) $gpoAlreadyExist.GpoStatus = 'ComputerSettingsDisabled' } #end If } #end If-Else Write-Debug -Message 'Add GPO-link to corresponding OU' $Splat = @{ GUID = $gpoAlreadyExist.Id Target = $PSBoundParameters['gpoLinkPath'] LinkEnabled = 'Yes' Server = $dcServer } if ($PSCmdlet.ShouldProcess("Linking GPO '$gpoName'", 'Link GPO?')) { New-GPLink @Splat } #end If # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Adding settings #Write-Host "Setting Screen saver timeout to 15 minutes" #Set-GPRegistryValue -Name $gpoName -key "HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop" -ValueName ScreenSaveTimeOut -Type String -value 900 #Write-Host "Enable Screen Saver" #Set-GPRegistryValue -Name $gpoName -key "HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop" -ValueName ScreenSaveActive -Type String -value 1 } else { Write-Verbose -Message (' {0} Policy already exist. Changing Permissions and disabling corresponding settings (User or Computer).' -f $gpoName ) # Give Rights to SL_GpoAdminRight Write-Debug -Message ('Add GpoAdminRight to {0}' -f $gpoName) $Splat = @{ GUID = $gpoAlreadyExist.Id PermissionLevel = 'GpoEditDeleteModifySecurity' TargetName = $GpoAdmin.SamAccountName TargetType = 'group' Server = $dcServer } if ($PSCmdlet.ShouldProcess("Giving permissions to GPO '$gpoName'", 'Confirm giving permissions?')) { Set-GPPermissions @Splat # WmiFilterFullControl # StarterGpoFullControl # SomWmiFilterFullControl # SomCreateGpo # SomCreateStarterGpo # SomLogging # SomPlanning # SomLink } #end If # Disable the corresponding Settings section of the GPO If ($gpoScope -eq 'C') { if ($PSCmdlet.ShouldProcess("Disabling Users section on GPO '$gpoName'", 'Confirm disabling user section?')) { Write-Debug -Message 'Disable Policy User Settings' $gpoAlreadyExist.GpoStatus = 'UserSettingsDisabled' } #end If } else { if ($PSCmdlet.ShouldProcess("Disabling Computers section on GPO '$gpoName'", 'Confirm disabling computer section?')) { Write-Debug -Message 'Disable Policy Computer Settings' $gpoAlreadyExist.GpoStatus = 'ComputerSettingsDisabled' } #end If } #end If-Else } # End If # Check if Backup needs to be imported if ($PSBoundParameters.ContainsKey('gpoBackupID') -and $PSBoundParameters.ContainsKey('gpoBackupPath')) { # Import the Backup Write-Debug -Message (' Importing GPO Backup {0} from path {1} to GPO {2}' -f $PSBoundParameters['gpoBackupID'], $PSBoundParameters['gpoBackupPath'], $gpoName ) Try { $Splat = @{ BackupId = $PSBoundParameters['gpoBackupID'] TargetGuid = $gpoAlreadyExist.Id path = $PSBoundParameters['gpoBackupPath'] } if ($PSCmdlet.ShouldProcess("Importing GPO Backup '$gpoBackupID' to GPO '$gpoName'", 'Confirm import')) { Import-GPO @Splat } #end If } Catch { Write-Error -Message ('No valid backup was found on {0}!' -f $PSBoundParameters['gpoBackupPath']) } #end Try-Catch } # End If } # End Process Section End { if ($null -ne $Variables -and $null -ne $Variables.Footer) { $txt = ($Variables.Footer -f $MyInvocation.InvocationName, 'creating GPO.' ) Write-Verbose -Message $txt } #end If return $gpoAlreadyExist } # End END Section } #end Function New-DelegatedAdGpo |