GPOTools.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\GPOTools.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced #$script:doDotSource = Get-PSFConfigValue -FullName GPOTools.Import.DoDotSource -Fallback $false $script:doDotSource = $false if ($GPOTools_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced #$importIndividualFiles = Get-PSFConfigValue -FullName GPOTools.Import.IndividualFiles -Fallback $false $importIndividualFiles = $false if ($GPOTools_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path "$($script:ModuleRoot)\..\.git") { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code <# This file loads the strings documents from the respective language folders. This allows localizing messages and errors. Load psd1 language files for each language you wish to support. Partial translations are acceptable - when missing a current language message, it will fallback to English or another available language. #> Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'GPOTools' -Language 'en-US' function ConvertFrom-ImportedIdentity { <# .SYNOPSIS Converts an imported identity into a security principal. .DESCRIPTION Converts an imported identity into a security principal. This is used for granting permissions. .PARAMETER Permission The permission object containing the source principal. .PARAMETER DomainObject An object representing the destination domain (as returned by Get-ADDomain) .EXAMPLE PS C:\> ConvertFrom-ImportedIdentity -Permission $permission -DomainObject $domainObject Resolves the source identity into a destination security principal. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] [OutputType([System.Security.Principal.IdentityReference])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Permission, [Parameter(Mandatory = $true)] $DomainObject ) process { switch ($Permission.PrincipalType) { 'Local BuiltIn' { return [System.Security.Principal.SecurityIdentifier]$Permission.SID } 'foreignSecurityPrincipal' { return [System.Security.Principal.SecurityIdentifier]$Permission.SID } 'group' { if ($Permission.IsBuiltIn -like 'true') { return [System.Security.Principal.SecurityIdentifier]('{0}-{1}' -f $DomainObject.DomainSID, $Permission.RID) } else { $identity = $script:identityMapping | Where-Object SID -EQ $Permission.SID if (-not $identity) { throw "Cannot resolve $($Permission.IdentityReference) ($($Permission.SID))" } return [System.Security.Principal.NTAccount]('{0}\{1}' -f $DomainObject.NetBIOSName, $identity.Target) } } } } } function ConvertTo-DnsDomainName { <# .SYNOPSIS Converts a distinguished name in the DNS domain name. .DESCRIPTION This extracts the domain portion of a distinguished name and processes it as dns name. .PARAMETER DistinguishedName The name to parse / convert. .EXAMPLE PS C:\> Get-ADDomain | ConvertTo-DnsDomainName Returns the dns name of the current domain. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] [Alias('Name')] [string[]] $DistinguishedName ) process { foreach ($distName in $DistinguishedName) { ($distName -split "," | Where-Object { $_ -like "DC=*" } | ForEach-Object { $_ -replace '^DC=' }) -join "." } } } function New-ImportResult { <# .SYNOPSIS Create unified import result objects. .DESCRIPTION Create unified import result objects. .PARAMETER Action The action taken. .PARAMETER Step The current step of the action. .PARAMETER Target The target of the step. .PARAMETER Success Whether the action was a success. .PARAMETER Data Any data to add to the report .PARAMETER ErrorData Any error data to add to the report .EXAMPLE PS C:\> New-ImportResult -Action 'Importing Policy Objects' -Step 'Import Object' -Target $gpoEntry -Success $true -Data $gpoEntry, $migrationTablePath Creates a new object representing a successful GPO import. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Action, [Parameter(Mandatory = $true)] [string] $Step, $Target, [Parameter(Mandatory = $true)] [bool] $Success, $Data, $ErrorData ) [pscustomobject]@{ PSTypeName = 'GPOTools.ImportResult' Action = $Action Step = $Step Target = $Target Success = $Success Data = $Data Error = $ErrorData } } function New-MigrationTable { <# .SYNOPSIS Creates a new migration table used for GPO imports. .DESCRIPTION Creates a new migration table used for GPO imports. In this table, all source identities get matched to fitting destination identities. This ensures, that all identity references within GPOs remain intact. .PARAMETER Path The path where to spawn the migration table. Specify a folder, the file will be named '<DomainName>.migtable' .PARAMETER BackupPath The path where the GPO backups are stored. .PARAMETER Domain The domain the backup will be restored to. Defaults to the current user's domain. .EXAMPLE PS C:\> New-MigrationTable -Path '.' -BackupPath '.' Creates a migration table in the current path and looks in the current path for backup folders. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [string] $BackupPath, [string] $Domain = $env:USERDNSDOMAIN ) begin { $resolvedPath = (Resolve-Path $Path).ProviderPath $resolvedBackupPath = (Resolve-Path $BackupPath).ProviderPath $writePath = Join-Path -Path $resolvedPath -ChildPath "$Domain.migtable" #region Resolving source and destination Domain Names $domainObject = Get-ADDomain -Server $Domain $destDomainDNS = $domainObject.DNSRoot $destDomainNetBios = $domainObject.NetBIOSName if ($script:sourceDomainData) { $sourceDomainDNS = $script:sourceDomainData.DomainDNSName $sourceDomainNetBios = $script:sourceDomainData.NetBIOSName } elseif ($script:identityMapping.Count -gt 0) { $sourceDomainDNS = $script:identityMapping[0].DomainFqdn $sourceDomainNetBios = $script:identityMapping[0].DomainName } else { throw "Unable to determine source domain. Run Import-GptDomainData or Import-GptIdentity first!" } #endregion Resolving source and destination Domain Names #region Preparing imported identities $explicitIdentityMappings = foreach ($identity in $script:identityMapping) { if (($identity.IsBuiltIn -eq 'True') -and ($identity.SID -like "*-32-*")) { [PSCustomObject]@{ Source = $identity.Name Target = $identity.Target } } else { [PSCustomObject]@{ Source = ('{0}\{1}' -f $sourceDomainNetBios, $identity.Name) Target = ('{0}\{1}' -f $destDomainNetBios, $identity.Target) } [PSCustomObject]@{ Source = ('{0}@{1}' -f $identity.Name, $sourceDomainDNS) Target = ('{0}@{1}' -f $identity.Target, $destDomainDNS) } } } #endregion Preparing imported identities } process { #region Preparing basic migration table $groupPolicyManager = New-Object -ComObject GPMgmt.GPM $migrationTable = $groupPolicyManager.CreateMigrationTable() $constants = $groupPolicyManager.getConstants() $backupDirectory = $groupPolicyManager.GetBackupDir($resolvedBackupPath) $backupList = $backupDirectory.SearchBackups($groupPolicyManager.CreateSearchCriteria()) foreach ($policyBackup in $backupList) { $migrationTable.Add(0, $policyBackup) $migrationTable.Add($constants.ProcessSecurity, $policyBackup) } #endregion Preparing basic migration table #region Applying identity and UNC mappings foreach ($entry in $migrationTable.GetEntries()) { switch ($entry.EntryType) { $constants.EntryTypeUNCPath { if ($entry.Source -like "\\$sourceDomainDNS\*") { $null = $migrationTable.UpdateDestination($entry.Source, $entry.Source.Replace("\\$sourceDomainDNS\", "\\$destDomainDNS\")) } if ($entry.Source -like "\\$sourceDomainNetBios\*") { $null = $migrationTable.UpdateDestination($entry.Source, $entry.Source.Replace("\\$sourceDomainNetBios\", "\\$destDomainNetBios\")) } } { $constants.EntryTypeUser, $constants.EntryTypeGlobalGroup, $constants.EntryTypeUniversalGroup, $constants.EntryTypeUnknown -contains $_ } { if ($mapping = $explicitIdentityMappings | Where-Object Source -EQ $entry.Source) { $null = $migrationTable.UpdateDestination($entry.Source, $mapping.Target) } } } } #endregion Applying identity and UNC mappings $migrationTable.Save($writePath) $writePath } } function Resolve-ADPrincipal { <# .SYNOPSIS Resolves an AD Principal into a common format. .DESCRIPTION Resolves an AD Principal into a common format. Optimized for use with cross-domain migration procedures. Caches successful results. Returns empty values on unresolved users. .PARAMETER Name Name of the principal to resolve. .PARAMETER Domain Domain to resolve it for. Read access is required. .EXAMPLE PS C:\> Resolve-ADPrincipal -Name 'contoso\max' -Domain 'contoso.com' Resolves the user max from contoso.com #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string[]] $Name, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Domain ) begin { if (-not $script:principals) { $script:principals = @{ } } if (-not $script:principals[$Domain]) { $script:principals[$Domain] = @{ } } $domainFQDN = (Get-ADDomain -Server $Domain).DNSRoot $domainName = (Get-ADDomain -Server $Domain).Name } process { foreach ($identity in $Name) { # Return form Cache if available if ($script:principals[$Domain][$identity]) { return $script:principals[$Domain][$identity] } #region Resolve User in AD if ($identity -as [System.Security.Principal.SecurityIdentifier]) { $adObject = Get-ADObject -Server $Domain -LDAPFilter "(objectSID=$identity)" -Properties ObjectSID } elseif (Test-IsDistinguishedName -Name $identity) { $adObject = Get-ADObject -Server ($identity | ConvertTo-DnsDomainName) -Identity $identity -Properties ObjectSID } elseif ($identity -like "*\*") { try { $sidName = ([System.Security.Principal.NTAccount]$identity).Translate([System.Security.Principal.SecurityIdentifier]) } catch { continue } $adObject = Get-ADObject -Server $Domain -LDAPFilter "(objectSID=$sidName)" -Properties ObjectSID if (-not $adObject) { $script:principals[$Domain][$identity] = [pscustomobject]@{ DistinguishedName = $null Name = $identity SID = $sidName.Value RID = $sidName.Value.ToString().Split("-")[-1] Type = 'Local BuiltIn' IsBuiltin = $true DomainName = $domainName DomainFqdn = $domainFQDN } $script:principals[$Domain][$identity] continue } } else { try { $sidName = ([System.Security.Principal.NTAccount]$identity).Translate([System.Security.Principal.SecurityIdentifier]) if ($sidName.Value -like 'S-1-3-*') { $script:principals[$Domain][$identity] = [pscustomobject]@{ DistinguishedName = $null Name = $identity SID = $sidName.Value RID = $sidName.Value.ToString().Split("-")[-1] Type = 'Local BuiltIn' IsBuiltin = $true DomainName = $domainName DomainFqdn = $domainFQDN } $script:principals[$Domain][$identity] continue } $adObject = Get-ADObject -Server $Domain -LDAPFilter "(objectSID=$sidName)" -Properties ObjectSID } catch { $adObject = Get-ADObject -Server $Domain -LDAPFilter "(Name=$identity)" -Properties ObjectSID } } if (-not $adObject -or -not $adObject.ObjectSID) { continue } #endregion Resolve User in AD $script:principals[$Domain][$identity] = [pscustomobject]@{ DistinguishedName = $adObject.DistinguishedName Name = $adObject.Name SID = $adObject.ObjectSID.Value RID = $adObject.ObjectSID.Value.ToString().Split("-")[-1] Type = $adObject.ObjectClass IsBuiltin = ((($adObject.ObjectSID.Value.Split("-")[-1] -as [int]) -lt 1000) -or ($adObject.ObjectSID.Value -like 'S-1-5-32-*')) DomainName = $domainName DomainFqdn = $domainFQDN } $script:principals[$Domain][$identity] } } } function Test-IsDistinguishedName { <# .SYNOPSIS Lightweight test to check whether a string is a distinguished name. .DESCRIPTION Lightweight test to check whether a string is a distinguished name. This check is done by checking, whether the string contains a "DC=" sequence. .PARAMETER Name The name to check. .EXAMPLE PS C:\> Test-IsDistinguishedName -Name $name returns whether $name is a distinguished name. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name ) process { $Name -match 'DC=' } } function Test-Overlap { <# .SYNOPSIS Matches N:N mappings for congruence. .DESCRIPTION Matches N:N mappings for congruence. Use this for comparing two arrays for overlap. This can be used for scenarios such as: - Whether n Items in Array One are equal to an Item in Array Two. - Whether n Items in Array One are similar to an Item in Array Two. This is especially designed to abstract filtering by multiple wildcard filters. .PARAMETER ReferenceObject The object(s) to compare .PARAMETER DifferenceObject The array of items to compare them to. .PARAMETER Property Compare a property, rather than the basic object. .PARAMETER Count The number of congruent items required for a successful result. Defaults to 1. .PARAMETER Operator How the comparison should be performed. Defaults to 'Equal' Supported Comparisons: Equal, Like, Match .EXAMPLE PS C:\> Test-Overlap -ReferenceObject $ReferenceObject -DifferenceObject $DifferenceObject Tests whether any item in the two arrays are equal. #> [OutputType([System.Boolean])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowNull()] $ReferenceObject, [Parameter(Mandatory = $true)] [AllowNull()] $DifferenceObject, [string] $Property, [int] $Count = 1, [ValidateSet('Equal', 'Like', 'Match')] [string] $Operator = 'Equal' ) begin { $parameter = @{ IncludeEqual = $true ExcludeDifferent = $true } if ($Property) { $parameter['Property'] = $Property } } process { switch ($Operator) { 'Equal' { return (Compare-Object -ReferenceObject $ReferenceObject -DifferenceObject $DebugPreference @parameter | Measure-Object).Count -ge $Count } 'Like' { $numberFound = 0 foreach ($reference in $ReferenceObject) { foreach ($difference in $DifferenceObject) { if ($Property -and ($reference.$Property -like $difference.$Property)) { $numberFound++ } elseif (-not $Property -and ($reference -like $difference)) { $numberFound++ } if ($numberFound -ge $Count) { return $true } } } return $false } 'Match' { $numberFound = 0 foreach ($reference in $ReferenceObject) { foreach ($difference in $DifferenceObject) { if ($Property -and ($reference.$Property -match $difference.$Property)) { $numberFound++ } elseif (-not $Property -and ($reference -match $difference)) { $numberFound++ } if ($numberFound -ge $Count) { return $true } } } return $false } } } } function Backup-GptPolicy { <# .SYNOPSIS Creates a full backup of all specified GPOs. .DESCRIPTION Creates a full backup of all specified GPOs. This includes permissions, settings, GPO Links and WMI Filter. .PARAMETER Path The path to the folder to export into. Folder must exist. .PARAMETER Name Filter Policy Objects by policy name. By default, ALL policies are targeted. .PARAMETER GpoObject Specify explicitly which GPOs to export. Accepts output of Get-GPO .PARAMETER Domain The source domain to export from. .PARAMETER Identity Additional identities to export. Identites are names of groups that are used for matching groups when importing policies. .EXAMPLE PS C:\> Backup-GptPolicy -Path . Export all policies to file. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string] $Name = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [string] $Domain = $env:USERDNSDOMAIN, [string[]] $Identity ) begin { $resolvedPath = (Resolve-Path -Path $Path).ProviderPath $policyFolder = New-Item -Path $resolvedPath -Name GPO -ItemType Directory -Force } process { $gpoObjects = $GpoObject if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain | Where-Object DisplayName -Like $Name } $gpoObjects | Export-GptObject -Path $policyFolder.FullName -Domain $Domain Export-GptLink -Path $resolvedPath -Domain $Domain $gpoObjects | Export-GptPermission -Path $resolvedPath -Domain $Domain $gpoObjects | Export-GptWmiFilter -Path $resolvedPath -Domain $Domain Export-GptIdentity -Path $resolvedPath -Domain $Domain -Name $Identity Export-GptDomainData -Path $resolvedPath -Domain $Domain } } function Export-GptDomainData { <# .SYNOPSIS Generates a summary export of the source domain. .DESCRIPTION Generates a summary export of the source domain. This data is required or useful in several import stages. .PARAMETER Path The path to export to. Point at an existing folder. .PARAMETER Domain The domain to export the info of. .EXAMPLE PS C:\> Export-GptDomainData -Path '.' Exports the current domain's basic info into the current folder. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Path, [string] $Domain = $env:USERDNSDOMAIN ) begin { $resolvedPath = (Resolve-Path -Path $Path).ProviderPath } process { $domainObject = Get-ADDomain -Server $Domain [pscustomobject]@{ Domain = $Domain DomainDNSName = $domainObject.DNSRoot NetBIOSName = $domainObject.NetBIOSName BackupVersion = '1.0.0' Timestamp = (Get-Date) DomainSID = $domainObject.DomainSID.Value } | Export-Clixml -Path (Join-Path -Path $resolvedPath -ChildPath 'backup.clixml') } } function Export-GptIdentity { <# .SYNOPSIS Exports identity data used for Group Policy imports. .DESCRIPTION Generates an export dump of identity information. This is later used during import of group policy objects: - To map between identities for permissions and policy content. - To translate localized builtin account names. - To correctly target renamed builtin acconts. .PARAMETER Path The path where the exprot should be stored in. Specify an existing folder. .PARAMETER Name Names of groups to include in addition to the builtin accounts. .PARAMETER Domain The domain to generate the dump from. .EXAMPLE PS C:\> Export-GptIdentity -Path '.' Export the builtin accounts into the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name, [string] $Domain = $env:USERDNSDOMAIN ) begin { $pdcEmulator = (Get-ADDomain -Server $Domain).PDCEmulator [System.Collections.ArrayList]$identities = @() } process { #region Process Builtin Accounts $builtInSID = 'S-1-5-32-544', 'S-1-5-32-545', 'S-1-5-32-546', 'S-1-5-32-548', 'S-1-5-32-549', 'S-1-5-32-550', 'S-1-5-32-551', 'S-1-5-32-552', 'S-1-5-32-554', 'S-1-5-32-555', 'S-1-5-32-556', 'S-1-5-32-557', 'S-1-5-32-558', 'S-1-5-32-559', 'S-1-5-32-560', 'S-1-5-32-561', 'S-1-5-32-562', 'S-1-5-32-568', 'S-1-5-32-569', 'S-1-5-32-573', 'S-1-5-32-574', 'S-1-5-32-575', 'S-1-5-32-576', 'S-1-5-32-577', 'S-1-5-32-578', 'S-1-5-32-579', 'S-1-5-32-580', 'S-1-5-32-582' $builtInRID = '498', '500', '501', '502', '512', '513', '514', '515', '516', '517', '518', '519', '520', '521', '522', '525', '526', '527', '553', '571', '572' $domainSID = (Get-ADDomain -Server $pdcEmulator).DomainSID.Value $identities.AddRange(($builtInSID | Resolve-ADPrincipal -Domain $Domain)) $identities.AddRange(($builtInRID | Resolve-ADPrincipal -Domain $Domain -Name { '{0}-{1}' -f $domainSID, $_ })) #endregion Process Builtin Accounts #region Process Additional Requested Accounts foreach ($adEntity in $Name) { #region Handle Wildcard Filters if ($adEntity.Contains("*")) { $identities.AddRange((Get-ADGroup -Server $pdcEmulator -LDAPFilter "(name=$adEntity)" | Resolve-ADPrincipal -Domain $Domain)) continue } #endregion Handle Wildcard Filters try { $principal = Resolve-ADPrincipal -Name $adEntity -Domain $Domain -ErrorAction Stop $null = $identities.Add($principal) } catch { Write-Error -Message "Failed to resolve Identity: $adEntity | $_" -Exception $_.Exception } } #endregion Process Additional Requested Accounts } end { $identities | Group-Object SID | ForEach-Object { $_.Group | Select-Object -First 1 } | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_Identities_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } } function Export-GptLink { <# .SYNOPSIS Generates a full dump of all GPO links. .DESCRIPTION Generates a full dump of all GPO links. This command will enumerate all OUs and create an export file of them. This is used to restore links of exported GPOs when restoring them. .PARAMETER Path The path in which to export the data. Specify an existing folder. .PARAMETER Domain The domain to retrieve the data from. .EXAMPLE PS C:\> Export-GptLink -Path . Exports all GPO links into the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string] $Domain = $env:USERDNSDOMAIN ) begin { $gpoObjects = Get-GPO -All -Domain $Domain } process { Get-ADOrganizationalUnit -Server $Domain -LdapFilter '(gpLink=*)' -Properties gpLink, CanonicalName | ForEach-Object { $indexCount = 0 $links = $_.gpLink -replace '\]\[', ']_[' -split '_' foreach ($link in $links) { $path, $state = $link -replace '\[LDAP://' -replace '\]$' -split ';' [PSCustomObject]@{ Path = $Path State = $state # 0: Normal, 1: Disabled, 2: Enforced GpoName = ($gpoObjects | Where-Object Path -EQ $path).DisplayName Domain = $Domain OUDN = $_.DistinguishedName OUName = $_.Name OUCanonical = $_.CanonicalName Index = $indexCount++ TotalCount = $links.Count } } } | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_Links_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } } function Export-GptObject { <# .SYNOPSIS Creates a backup of all specified GPOs. .DESCRIPTION Creates a backup of all specified GPOs. .PARAMETER Path The path in which to generate the Backup. .PARAMETER Name The name to filter GPOs by. By default, ALL GPOs are exported. .PARAMETER GpoObject Select the GPOs to export by specifying the explicit GPO object to export. .PARAMETER Domain The domain from which to export the GPOs .EXAMPLE PS C:\> Export-GptObject -Path . Generate a GPO export of all GPOs in the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [string] $Domain = $env:USERDNSDOMAIN ) process { $gpoObjects = $GpoObject | Where-Object { Test-Overlap -ReferenceObject $_.DisplayName -DifferenceObject $Name -Operator Like } if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain | Where-Object { Test-Overlap -ReferenceObject $_.DisplayName -DifferenceObject $Name -Operator Like } } $null = $gpoObjects | Backup-GPO -Path (Resolve-Path $Path).ProviderPath $gpoObjects | Select-Object DisplayName, ID, Owner, CreationTime, ModificationTime, WmiFilter | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_object_$($Domain).csv") -Encoding UTF8 -NoTypeInformation -Append } } function Export-GptPermission { <# .SYNOPSIS Export the permissions assigned on GPOs .DESCRIPTION Export the permissions assigned on GPOs. Note: This command is currently fairly slow so give it some time. .PARAMETER Path The path where to create the export. Must be an existing folder. .PARAMETER Name Filter GPOs to process by name. .PARAMETER GpoObject Specify GPOs to process by object. .PARAMETER IncludeInherited Include inherited permissions in the export. By default, only explicit permissiosn are exported. Note: By default, all GPOs in a windows domain only have explicit permissions set. This will have little impact in most scenarios. .PARAMETER Domain The domain to export from. .EXAMPLE PS C:\> Export-GptPermission -Path '.' Exports permissions of all GPOs into the current folder. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -Path $_ })] [string] $Path, [string] $Name = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [switch] $IncludeInherited, [string] $Domain = $env:USERDNSDOMAIN ) begin { Write-Verbose "Preparing Filters" $select_Name = @{ name = 'GpoName'; expression = { $gpoItem.DisplayName } } $select_Path = @{ name = 'GpoPath'; expression = { $gpoItem.Path } } $select_SID = @{ name = 'SID'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).SID } } $select_RID = @{ name = 'RID'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).RID } } $select_IsBuiltin = @{ name = 'IsBuiltIn'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).IsBuiltIn } } $select_PrincipalType = @{ name = 'PrincipalType'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).Type } } [System.Collections.ArrayList]$accessList = @() } process { Write-Verbose "Resolving Policies to process" $gpoObjects = $GpoObject if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain | Where-Object DisplayName -Like $Name } Write-Verbose "Found $($gpoObjects.Count) Policies" $accessData = foreach ($gpoItem in $gpoObjects) { Write-Verbose "Processing policy: $($gpoItem.DisplayName)" $adObject = Get-ADObject -Identity $gpoItem.Path -Server $gpoItem.DomainName -Properties ntSecurityDescriptor $adObject.ntSecurityDescriptor.Access | Where-Object { $IncludeInherited -or -not $_.IsInherited } | Select-Object $select_Name, $select_Path, '*', $select_SID, $select_RID, $select_IsBuiltin, $select_PrincipalType } Write-Verbose "Found $($accessData.Count) permission entries." $null = $accessList.AddRange($accessData) } end { Write-Verbose "Exorting to file" $accessList | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_permissions_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } } function Export-GptWmiFilter { <# .SYNOPSIS Export WMI Filters. .DESCRIPTION Export WMI Filters. WMI Filters to export are picked up by the GPÜO they are assigned to. Unassigned filters are ignored. .PARAMETER Path The path where to create the export. Must be an existing folder. .PARAMETER Name Filter GPOs to process by name. .PARAMETER GpoObject Specify GPOs to process by object. .PARAMETER Domain The domain to export from. .EXAMPLE PS C:\> Export-GptWmiFilter -Path '.' Export all WMI Filters of all GPOs into the current folder. #> [CmdletBinding()] param ( [ValidateScript({ Test-Path -Path $_ })] [Parameter(Mandatory = $true)] [string] $Path, [string] $Name = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [string] $Domain = $env:USERDNSDOMAIN ) begin { $wmiPath = "CN=SOM,CN=WMIPolicy,$((Get-ADDomain -Server $Domain).SystemsContainer)" $allFilterHash = @{ } $foundFilterHash = @{ } Get-ADObject -Server $Domain -SearchBase $wmiPath -Filter { objectClass -eq 'msWMI-Som' } -Properties msWMI-Author, msWMI-Name, msWMI-Parm1, msWMI-Parm2 | ForEach-Object { $allFilterHash[$_.'msWMI-Name'] = [pscustomobject]@{ Author = $_.'msWMI-Author' Name = $_.'msWMI-Name' Description = $_.'msWMI-Parm1' Filter = $_.'msWMI-Parm2' } } } process { $gpoObjects = $GpoObject if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain | Where-Object DisplayName -Like $Name } foreach ($filterName in $gpoObjects.WmiFilter.Name) { $foundFilterHash[$filterName] = $allFilterHash[$filterName] } } end { $foundFilterHash.Values | Where-Object { $_ } | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_wmifilters_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } } function Import-GptDomainData { <# .SYNOPSIS Imports domain information of the source domain. .DESCRIPTION Imports domain information of the source domain. .PARAMETER Path The path to the file or the folder it resides in. .EXAMPLE PS C:\> Import-GptDomainData -Path '.' Import the domain information file from the current folder. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Path ) begin { $pathItem = Get-Item -Path $Path if ($pathItem.Extension -eq '.clixml') { $resolvedPath = $pathItem.FullName } else { $resolvedPath = (Get-ChildItem -Path $pathItem.FullName -Filter 'backup.clixml' | Select-Object -First 1).FullName } if (-not $resolvedPath) { throw "Could not find a domain data file in $($pathItem.FullName)" } } process { $script:sourceDomainData = Import-Clixml $resolvedPath } } function Import-GptIdentity { <# .SYNOPSIS Imports identity data exported from the source domain. .DESCRIPTION Imports identity data exported from the source domain. This data is used for mapping source identities to destination identities. .PARAMETER Path The path where to pick up the file. .PARAMETER Name Filter identities by name. .PARAMETER Domain The destination domain that later GPOs will be imported to. .PARAMETER Mapping A mapping hashtable allowing you to map identities that have unequal names. .EXAMPLE PS C:\> Import-GptIdentity -Path '.' Import the identity export file from the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -Path $_ })] [string] $Path, [string[]] $Name = '*', [string] $Domain = $env:USERDNSDOMAIN, [System.Collections.IDictionary] $Mapping = @{ } ) begin { $pathItem = Get-Item -Path $Path if ($pathItem.Extension -eq '.csv') { $resolvedPath = $pathItem.FullName } else { $resolvedPath = (Get-ChildItem -Path $pathItem.FullName -Filter 'gp_Identities*.csv' | Select-Object -First 1).FullName } if (-not $resolvedPath) { throw "Could not find identities file in $($pathItem.FullName)" } $domainSID = (Get-ADDomain -Server $Domain).DomainSID.Value # Declare Module scope index of identities and what they map to $script:identityMapping = New-Object 'System.Collections.Generic.List[Object]' # Helpful Select Hashtables $select_TargetMapping = @{ Name = 'Target' Expression = { $Mapping[$importEntry.Name] } } $select_TargetName = @{ Name = 'Target' Expression = { $targetName } } } process { $importData = Import-Csv -Path $resolvedPath foreach ($importEntry in $importData) { # Skip entries filtered out if (-not (Test-Overlap -ReferenceObject $importEntry.Name -DifferenceObject $Name -Operator Like)) { continue } #region Case: Mapped Entry if ($Mapping[$importEntry.Name]) { $script:identityMapping.Add(($importEntry | Select-Object *, $select_TargetMapping)) } #endregion Case: Mapped Entry #region Case: Discovery else { #region Case: Native BuiltIn Principal if (($importEntry.IsBuiltIn -eq 'True') -and ($importEntry.SID -like "*-32-*")) { try { $targetName = ([System.Security.Principal.SecurityIdentifier]$importEntry.SID).Translate([System.Security.Principal.NTAccount]).Value } catch { Write-Warning "Failed to translate identity: $($importEntry.Name) ($($importEntry.SID))" continue } $script:identityMapping.Add(($importEntry | Select-Object *, $select_TargetName)) } #endregion Case: Native BuiltIn Principal #region Case: Domain Specific BuiltIn Principal elseif ($importEntry.IsBuiltIn -eq 'True') { $targetSID = '{0}-{1}' -f $domainSID, $importEntry.RID $adObject = Get-ADObject -Server $Domain -LDAPFilter "(&(objectClass=$($importEntry.Type))(objectSID=$($targetSID)))" if (-not $adObject) { Write-Warning "Failed to resolve AD identity: $($importEntry.Name) ($($targetSID))" continue } $targetName = $adObject.Name $script:identityMapping.Add(($importEntry | Select-Object *, $select_TargetName)) } #endregion Case: Domain Specific BuiltIn Principal #region Case: Custom Principal else { $adObject = Get-ADObject -Server $Domain -LDAPFilter "(&(objectClass=$($importEntry.Type))(name=$($importEntry.Name)))" if (-not $adObject) { Write-Warning "Failed to resolve AD identity: $($importEntry.Name)" continue } $targetName = $adObject.Name $script:identityMapping.Add(($importEntry | Select-Object *, $select_TargetName)) } #endregion Case: Custom Principal } #endregion Case: Discovery } } } function Import-GptLink { <# .SYNOPSIS Imports GPO Links. .DESCRIPTION Imports GPO Links. Use this to restore the exported links in their original order (or as close to it as possible). .PARAMETER Path The path from which to pick up the import file. .PARAMETER Name Only restore links of matching GPOs .PARAMETER Domain The domain into which to import. .EXAMPLE PS C:\> Import-GptLink -Path '.' Import GPO Links based on the exported links stored in the current path. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name = '*', [string] $Domain = $env:USERDNSDOMAIN ) begin { #region Utility Functions function Get-OU { <# .SYNOPSIS Retrieves an OU. Caches results. .DESCRIPTION Retrieves an OU. Caches results. Results are cached separately for each domain/server. .PARAMETER DistinguishedName The name of the OU to check. .PARAMETER Server The domain or server to check against. .EXAMPLE PS C:\> Get-OU -DistinguishedName $dn -Server $Domain Return the OU pointed at with $dn if it exists. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $DistinguishedName, [Parameter(Mandatory = $true)] [string] $Server ) if (-not $script:targetOUs) { $script:targetOUs = @{ } } if (-not $script:targetOUs[$Server]) { $script:targetOUs[$Server] = @{ } } if ($script:targetOUs[$Server].ContainsKey($DistinguishedName)) { return $script:targetOUs[$Server][$DistinguishedName] } try { $paramGetADOrganizationalUnit = @{ Identity = $DistinguishedName Server = $Server Properties = 'gpLink' ErrorAction = 'Stop' } $script:targetOUs[$Server][$DistinguishedName] = Get-ADOrganizationalUnit @paramGetADOrganizationalUnit } catch { $script:targetOUs[$Server][$DistinguishedName] = $null } return $script:targetOUs[$Server][$DistinguishedName] } function Set-GPLinkSet { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( $LinkObject, $Domain, $AllGpos, $Server ) foreach ($linkItem in $LinkObject) { $linkItem.Index = [int]($linkItem.Index) $linkItem.TotalCount = [int]($linkItem.TotalCount) } $orgUnit = Get-OU -DistinguishedName $LinkObject[0].TargetOU -Server $Domain $insertIndex = 1 foreach ($linkItem in ($LinkObject | Sort-Object Index)) { if ($orgUnit.LinkedGroupPolicyObjects -contains $linkItem.Policy.CleanedPath) { $insertIndex = $orgUnit.LinkedGroupPolicyObjects.IndexOf($linkItem.Policy.CleanedPath) + 1 continue } $paramSetGPLink = @{ LinkEnabled = 'Yes' Guid = $linkItem.Policy.ID Order = $insertIndex Domain = $Domain Enforced = 'No' Target = $orgUnit Server = $Server ErrorAction = 'Stop' } if ($linkItem.State -eq "1") { $paramSetGPLink['LinkEnabled'] = 'No' } if ($linkItem.State -eq "2") { $paramSetGPLink['Enforced'] = 'Yes' } try { $null = New-GPLink @paramSetGPLink New-ImportResult -Action 'Importing Group Policy Links' -Step 'Applying Link' -Target $linkItem.GpoName -Data $linkItem -Success $true } catch { New-ImportResult -Action 'Importing Group Policy Links' -Step 'Applying Link' -Target $linkItem.GpoName -Data $linkItem -Success $false -ErrorData $_ } $insertIndex++ } } #endregion Utility Functions $PSDefaultParameterValues['New-ImportResult:Action'] = 'Importing Group Policy Links' $PSDefaultParameterValues['New-ImportResult:Success'] = $false $pathItem = Get-Item -Path $Path if ($pathItem.Extension -eq '.csv') { $resolvedPath = $pathItem.FullName } else { $resolvedPath = (Get-ChildItem -Path $pathItem.FullName -Filter 'gp_links_*.csv' | Select-Object -First 1).FullName } if (-not $resolvedPath) { throw "Could not find GPO Links file in $($pathItem.FullName)" } $domainObject = Get-ADDomain -Server $Domain $policyObjects = Get-GPO -All -Domain $Domain | Select-Object *, @{ Name = 'CleanedPath' Expression = { $_.Path -replace $_.ID, $_.ID } } $linkData = Import-Csv $resolvedPath | Where-Object { Test-Overlap -ReferenceObject $_.GpoName -DifferenceObject $Name -Operator Like } | Select-Object *, @{ Name = "Policy" Expression = { $linkItem = $_ $policyObjects | Where-Object DisplayName -EQ $linkItem.GpoName } }, @{ Name = "TargetOU" Expression = { '{0},{1}' -f ($_.OUDN -replace ',DC=\w+'), $domainObject.DistinguishedName } } } process { $groupedLinks = $linkData | Group-Object -Property GpoName $groupedLinks | Where-Object Name -NotIn $policyObjects.DisplayName | ForEach-Object { New-ImportResult -Step 'Checking GPO existence' -Target $_.Name -Data $_.Group -ErrorData "GPO $($_.Name) does not exist" } $linksPolicyExists = ($groupedLinks | Where-Object Name -In $policyObjects.DisplayName).Group $linksPolicyExists | Where-Object { -not (Get-OU -DistinguishedName $_.TargetOU -Server $Domain) } | ForEach-Object { New-ImportResult -Step 'Checking OU existence' -Target $_.GpoName -Data $_ -ErrorData "OU $($_.TargetOU) does not exist, cannot link $($_.GpoName)" } $linksToProcess = $linksPolicyExists | Where-Object { Get-OU -DistinguishedName $_.TargetOU -Server $Domain } $groupedToProcess = $linksToProcess | Group-Object -Property TargetOU foreach ($linkSet in $groupedToProcess) { Set-GPLinkSet -LinkObject $linkSet.Group -Domain $domainObject.DNSRoot -AllGpos $policyObjects -Server $domainObject.PDCEmulator } } } function Import-GptObject { <# .SYNOPSIS Import Group Policy Objects previously exported using Export-GptObject. .DESCRIPTION Import Group Policy Objects previously exported using Export-GptObject. .PARAMETER Path The path where the GPO export folders are located. Note: GPO export folders have a GUID as name. .PARAMETER Name Only import GPOs with a matching name. .PARAMETER Domain THe destination domain to import into. .EXAMPLE PS C:\> Import-GptObject -Path '.' Import all GPO objects exported into the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name = '*', [string] $Domain = $env:USERDNSDOMAIN ) begin { $pdcEmulator = (Get-ADDomain -Server $Domain).PDCEmulator if (-not (Test-Path $Path)) { New-ImportResult -Action 'Importing Policy Objects' -Step 'Validating import path' -Target $Path -Success $false throw "Import path not found: $Path" } if ((Get-Item -Path $Path).Extension -eq '.csv') { $gpoFile = Get-Item -Path $Path } elseif (Test-Path -Path (Join-Path -Path $Path -ChildPath 'gp_object_*.csv')) { $gpoFile = Get-Item (Join-Path -Path $Path -ChildPath 'gp_object_*.csv') } elseif (Test-Path -Path (Join-Path -Path (Join-Path -Path $Path -ChildPath 'GPO') -ChildPath 'gp_object_*.csv')) { $gpoFile = Get-Item (Join-Path -Path (Join-Path -Path $Path -ChildPath 'GPO') -ChildPath 'gp_object_*.csv') } else { New-ImportResult -Action 'Importing Policy Objects' -Step 'Validating import path' -Target $Path -Success $false throw "Could not find GPO backup index under: $Path" } $gpoData = Import-Csv -Path $gpoFile.FullName try { $migrationTablePath = New-MigrationTable -Path $gpoFile.DirectoryName -BackupPath $gpoFile.DirectoryName -Domain $Domain -ErrorAction Stop } catch { New-ImportResult -Action 'Importing Policy Objects' -Step 'Creating Migration Table' -Target $Path -Success $false -ErrorData $_ throw } } process { foreach ($gpoEntry in $gpoData) { if (-not (Test-Overlap -ReferenceObject $gpoEntry.DisplayName -DifferenceObject $Name -Operator Like)) { continue } $paramImportGPO = @{ Domain = $Domain Server = $pdcEmulator BackupGpoName = $gpoEntry.DisplayName TargetName = $gpoEntry.DisplayName Path = $gpoFile.DirectoryName MigrationTable = $migrationTablePath CreateIfNeeded = $true ErrorAction = 'Stop' } try { Write-Verbose "Importing Policy object: $($gpoEntry.DisplayName)" $importedGPO = Import-GPO @paramImportGPO if ($gpoEntry.WmiFilter) { $wmiFilter = Get-ADObject -SearchBase "CN=SOM,CN=WMIPolicy,$((Get-ADDomain -Server $pdcEmulator).SystemsContainer)" -LDAPFilter "(&(objectClass=msWMI-Som)(msWMI-Name=$($gpoEntry.WmiFilter)))" Set-ADObject -Identity $importedGPO.Path -Replace @{ gPCWQLFilter = "[$Domain;$($wmiFilter.Name);0]" } -Server $pdcEmulator } New-ImportResult -Action 'Importing Policy Objects' -Step 'Import Object' -Target $gpoEntry -Success $true -Data $gpoEntry, $migrationTablePath } catch { New-ImportResult -Action 'Importing Policy Objects' -Step 'Import Object' -Target $gpoEntry -Success $false -Data $gpoEntry, $migrationTablePath -ErrorData $_ Write-Error $_ } } } } function Import-GptPermission { <# .SYNOPSIS Import permissions to GPOs. .DESCRIPTION Import permissions to GPOs. This tries to restore the same permissions that existed on the GPOs before the export. Notes: - It is highly recommended to perform this before executing Import-GptLink. - Executing this requires the identities to have been imported (Import-GptIdentity) .PARAMETER Path The path where the permission export file is stored. .PARAMETER Name Only restore permissions for GPOs with a matching name. .PARAMETER GpoObject Select the GPOs to restore permissions to by specifying their full object. .PARAMETER ExcludeInherited Do not import permissions that were inherited permissions on the source GPO .PARAMETER Domain The domain to restore the GPO permissions to. .EXAMPLE PS C:\> Import-GptPermission -Path '.' Import GPO permissions from the current path. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -Path $_ })] [string] $Path, [string[]] $Name = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [switch] $ExcludeInherited, [string] $Domain = $env:USERDNSDOMAIN ) begin { #region Utility Functions function Update-GpoPermission { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( $ADObject, $Permission, $GpoObject, $DomainObject ) try { $accessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList @( (ConvertFrom-ImportedIdentity -Permission $Permission -DomainObject $DomainObject), $Permission.ActiveDirectoryRights, $Permission.AccessControlType, $Permission.ObjectType, $Permission.InheritanceType, $Permission.InheritedObjectType ) } catch { New-ImportResult -Action 'Update Gpo Permission' -Step 'Resolving Identity' -Target $Permission.GpoName -Success $false -Data $Permission -ErrorData $_ return } $matchingRule = $null $matchingRule = $ADObject.ntSecurityDescriptor.Access | Where-Object { $accessRule.IdentityReference -eq $_.IdentityReference -and $accessRule.ActiveDirectoryRights -eq $_.ActiveDirectoryRights -and $accessRule.AccessControlType -eq $_.AccessControlType -and $accessRule.ObjectType -eq $_.ObjectType -and $accessRule.InheritanceType -eq $_.InheritanceType -and $accessRule.InheritedObjectType -eq $_.InheritedObjectType } if ($matchingRule) { New-ImportResult -Action 'Update Gpo Permission' -Step 'Skipped, already exists' -Target $Permission.GpoName -Success $true -Data $Permission, $accessRule return } #region Set AD Permissions try { Write-Verbose "Updating ACL on GPO $($ADObject.DistinguishedName)" $acl = Get-Acl -Path "AD:\$($ADObject.DistinguishedName)" -ErrorAction Stop $acl.AddAccessRule($accessRule) $acl | Set-Acl -Path "AD:\$($ADObject.DistinguishedName)" -ErrorAction Stop } catch { New-ImportResult -Action 'Update Gpo Permission' -Step 'Apply AD Permission' -Target $Permission.GpoName -Success $false -Data $Permission, $accessRule -ErrorData $_ continue } #endregion Set AD Permissions #region Set File Permissions if (-not (Test-Path $ADObject.gPCFileSysPath)) { New-ImportResult -Action 'Update Gpo Permission' -Step 'Apply File Permission' -Target $Permission.GpoName -Success $false -Data $Permission, $accessRule -ErrorData "Path not found" continue } try { $rights = 'Read' if ($accessRule.ActiveDirectoryRights -eq 983295) { $rights = 'FullControl' } $fileRule = New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList @( $accessRule.IdentityReference $rights $accessRule.AccessControlType ) $acl = Get-Acl -Path $ADObject.gPCFileSysPath -ErrorAction Stop $acl.AddAccessRule($fileRule) $acl | Set-Acl -Path $ADObject.gPCFileSysPath -ErrorAction Stop } catch { [pscustomobject]@{ Action = 'Update Gpo Permission' Step = 'Apply File Permission' Target = $Permission.GpoName Success = $false Data = $Permission Data2 = $accessRule Error = $_ } } #endregion Set File Permissions New-ImportResult -Action 'Update Gpo Permission' -Step Success -Target $Permission.GpoName -Success $true -Data $Permission, $accessRule } #endregion Utility Functions $pathItem = Get-Item -Path $Path if ($pathItem.Extension -eq '.csv') { $resolvedPath = $pathItem.FullName } else { $resolvedPath = (Get-ChildItem -Path $pathItem.FullName -Filter 'gp_permissions_*.csv' | Select-Object -First 1).FullName } if (-not $resolvedPath) { throw "Could not find permissions file in $($pathItem.FullName)" } if (-not $script:identityMapping) { throw 'Could not find imported identities to match. Please run Import-GptIdentitiy first!' } $domainObject = Get-ADDomain -Server $Domain $allPermissionData = Import-Csv -Path $resolvedPath } process { $gpoObjects = $GpoObject if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain } foreach ($gpoItem in $gpoObjects) { if (-not (Test-Overlap -ReferenceObject $gpoItem.DisplayName -DifferenceObject $Name -Operator Like)) { continue } $adObject = Get-ADObject -Identity $gpoItem.Path -Server $gpoItem.DomainName -Properties ntSecurityDescriptor, gPCFileSysPath foreach ($permission in $allPermissionData) { # Skip items that do not apply if ($permission.GpoName -ne $gpoItem.DisplayName) { continue } if ($ExcludeInherited -and $permission.IsInherited -eq "True") { continue } Update-GpoPermission -ADObject $adObject -Permission $permission -GpoObject $gpoItem -DomainObject $domainObject } } } } function Import-GptWmiFilter { <# .SYNOPSIS Imports WMI filters. .DESCRIPTION Imports WMI filters stored to file using Export-GptWmiFilter. Note: This should be performed before using Import-GptPolicy. .PARAMETER Path The path from which to import the WmiFilters .PARAMETER Domain The domain into which to import the WmiFilters .EXAMPLE PS C:\> Import-GptWmiFilter -Path '.' Import WMI Filters from the current path. #> [CmdletBinding()] param ( [ValidateScript({ Test-Path -Path $_ })] [Parameter(Mandatory = $true)] [string] $Path, [string] $Domain = $env:USERDNSDOMAIN ) begin { $pathItem = Get-Item -Path $Path if ($pathItem.Extension -eq '.csv') { $resolvedPath = $pathItem.FullName } else { $resolvedPath = (Get-ChildItem -Path $pathItem.FullName -Filter 'gp_wmifilters_*.csv' | Select-Object -First 1).FullName } if (-not $resolvedPath) { throw "Could not find WMI Filters file in $($pathItem.FullName)" } $allWmiFilterEntries = Import-Csv -Path $resolvedPath $namingContext = (Get-ADRootDSE -Server $Domain).DefaultNamingContext $pdcEmulator = (Get-ADDomain -Server $Domain).PDCEmulator } process { foreach ($wmiFilter in $allWmiFilterEntries) { #region Update Existing if ($adObject = Get-ADObject -Server $pdcEmulator -LDAPFilter "(&(objectClass=msWMI-Som)(msWMI-Name=$($wmiFilter.Name)))") { $adObject | Set-ADObject -Server $pdcEmulator -Replace @{ 'msWMI-Author' = $wmiFilter.Author 'msWMI-Parm1' = $wmiFilter.Description 'msWMI-Parm2' = $wmiFilter.Filter } } #endregion Update Existing #region Create New else { $wmiGuid = "{$([System.Guid]::NewGuid())}" $creationDate = (Get-Date).ToUniversalTime().ToString("yyyyMMddhhmmss.ffffff-000") $attributes = @{ "showInAdvancedViewOnly" = "TRUE" "msWMI-Name" = $wmiFilter.Name "msWMI-Parm1" = $wmiFilter.Description "msWMI-Parm2" = $wmiFilter.Filter "msWMI-Author" = $wmiFilter.Author "msWMI-ID" = $wmiGuid "instanceType" = 4 "distinguishedname" = "CN=$wmiGuid,CN=SOM,CN=WMIPolicy,CN=System,$namingContext" "msWMI-ChangeDate" = $creationDate "msWMI-CreationDate" = $creationDate } $paramNewADObject = @{ OtherAttributes = $attributes Name = $wmiGuid Type = "msWMI-Som" Path = "CN=SOM,CN=WMIPolicy,CN=System,$namingContext" Server = $pdcEmulator } $null = New-ADObject @paramNewADObject } #endregion Create New } } } function Restore-GptPolicy { <# .SYNOPSIS Performs a full restore of GPOs exported with Backup-GptPolicy. .DESCRIPTION Performs a full restore of GPOs exported with Backup-GptPolicy. This includes executing all the relevant import commands in the optimal order. .PARAMETER Path The root path into which the backup was exported. .PARAMETER Name Only restore GPOs with matching name. .PARAMETER Domain The domain into which to restore the policy objects. .PARAMETER IdentityMapping A hashtable mapping source identities to destination identities. Use this to map groups that do not share the same name between source and destination. .EXAMPLE PS C:\> Restore-GptPolicy -Path '.' Perform a full restore/import of the backup written to the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name = '*', [string] $Domain = $env:USERDNSDOMAIN, [hashtable] $IdentityMapping = @{} ) begin { $common = @{ Path = $Path Domain = $Domain } Write-Verbose "Importing Domain Data" Import-GptDomainData -Path $Path } process { Write-Verbose "Importing Identities" Import-GptIdentity @common -Name $Name -Mapping $IdentityMapping Write-Verbose "Importing WMI Filters" Import-GptWmiFilter @common Write-Verbose "Importing Objects" Import-GptObject @common -Name $Name Write-Verbose "Importing Permissions" Import-GptPermission @common -Name $Name Write-Verbose "Importing GPO Links" Import-GptLink @common -Name $Name } } <# This is an example configuration file By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> <# # Example Configuration Set-PSFConfig -Module 'GPOTools' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" #> Set-PSFConfig -Module 'GPOTools' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'GPOTools' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." <# Stored scriptblocks are available in [PsfValidateScript()] attributes. This makes it easier to centrally provide the same scriptblock multiple times, without having to maintain it in separate locations. It also prevents lengthy validation scriptblocks from making your parameter block hard to read. Set-PSFScriptblock -Name 'GPOTools.ScriptBlockName' -Scriptblock { } #> <# # Example: Register-PSFTeppScriptblock -Name "GPOTools.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } #> <# # Example: Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name GPOTools.alcohol #> New-PSFLicense -Product 'GPOTools' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2019-06-04") -Text @" Copyright (c) 2019 Friedrich Weinmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |