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 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' { #TODO: Implement Domain Resolution try { $domainObject = Resolve-DomainMapping -DomainSid ($Permission.SID -as [System.Security.Principal.SecurityIdentifier]).AccountDomainSid.Value -DomainFqdn $Permission.DomainFqdn -DomainName $Permission.DomainName } catch { throw "Cannot resolve domain $($Permission.DomainFqdn) for $($Permission.Group) $($Permission.SID)! $_" } 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 Get-DomainData { <# .SYNOPSIS Retrieves common domain data, while caching results. .DESCRIPTION Retrieves common domain data, while caching results. Reduces overhead of looking up the same object again and again. .PARAMETER Domain The domain to retrieve data for. .EXAMPLE PS C:\> Get-DomainData -Domain Contoso.com Returns domain data for the domain contoso.com #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Domain ) begin { if (-not $script:domainData) { $script:domainData = @{ } #region Pre-Seed information for all domains in forest $forestObject = Get-ADForest $domains = $forestObject.Domains | Foreach-Object { Get-ADDomain -Server $_ -Identity $_ } | ForEach-Object { [PSCustomObject]@{ DistinguishedName = $_.DistinguishedName Name = $_.Name SID = $_.DomainSID Fqdn = $_.DNSRoot ADObject = $_ } } foreach ($domainObject in $domains) { $script:domainData["$($domainObject.SID)"] = $domainObject $script:domainData[$domainObject.Fqdn] = $domainObject $script:domainData[$domainObject.DistinguishedName] = $domainObject } #endregion Pre-Seed information for all domains in forest } } process { if ($script:domainData[$Domain]) { return $script:domainData[$Domain] } #region Collect information for unknown domain if ($Domain -as [System.Security.Principal.SecurityIdentifier]) { $domainObject = Get-ADDomain -Identity $Domain -ErrorAction Stop } else { $domainObject = Get-ADDomain -Server $Domain -ErrorAction Stop } $domainObjectProcessed = [PSCustomObject]@{ DistinguishedName = $domainObject.DistinguishedName Name = $domainObject.Name SID = $domainObject.DomainSID Fqdn = $domainObject.DNSRoot ADObject = $domainObject } $script:domainData["$($domainObjectProcessed.SID)"] = $domainObjectProcessed $script:domainData[$domainObjectProcessed.Fqdn] = $domainObjectProcessed $script:domainData[$domainObjectProcessed.DistinguishedName] = $domainObjectProcessed $script:domainData[$Domain] = $domainObjectProcessed $script:domainData[$Domain] #endregion Collect information for unknown domain } } 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 $domainData = Get-DomainData -Domain $Domain $destDomainDNS = $domainData.Fqdn $destDomainNetBios = $domainData.ADObject.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 $identity.DomainName, $identity.Name) Target = ('{0}\{1}' -f $identity.TargetDomain.Name, $identity.Target) } [PSCustomObject]@{ Source = ('{0}@{1}' -f $identity.Name, $identity.DomainFqdn) Target = ('{0}@{1}' -f $identity.Target, $identity.TargetDomain.DNSRoot) } } } #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) } } } } # Additionally scan backup for share mappings, as those won't be found by default foreach ($gpoFolder in (Get-ChildItem -Path $resolvedBackupPath -Directory | Where-Object Name -Match '^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$')) { $driveXmlPath = Join-Path -Path $gpoFolder.FullName -ChildPath 'DomainSysvol\GPO\User\Preferences\Drives\Drives.xml' if (-not (Test-Path -Path $driveXmlPath)) { continue } try { $driveXmlData = [xml](Get-Content -Path $driveXmlPath) } catch { continue } foreach ($driveSet in $driveXmlData.Drives.Drive) { if ($driveSet.Properties.Path -like "\\$sourceDomainDNS\*") { $null = $migrationTable.AddEntry($driveSet.Properties.Path, $constants.EntryTypeUNCPath, $driveSet.Properties.Path.Replace("\\$sourceDomainDNS\", "\\$destDomainDNS\")) } if ($driveSet.Properties.Path -like "\\$sourceDomainNetBios\*") { $null = $migrationTable.AddEntry($driveSet.Properties.Path, $constants.EntryTypeUNCPath, $driveSet.Properties.Path.Replace("\\$sourceDomainNetBios\", "\\$destDomainNetBios\")) } } } #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 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '')] [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 = @{ } } $principalsToIgnore = @( # .NET Account sids, that are shared across all domains and need no translation 'S-1-5-82-3876422241-1344743610-1729199087-774402673-2621913236' 'S-1-5-82-271721585-897601226-2024613209-625570482-296978595' # Everyone, as it is 100% generic and has no domain-prefix 'S-1-1-0' # NT Authority SIDs, as SID-to-SID need no translation, localization can be an issue 'S-1-5-18' 'S-1-5-19' 'S-1-5-20' ) $defaultDomainData = Get-DomainData -Domain $Domain $defaultDomainFQDN = $defaultDomainData.Fqdn $defaultDomainName = $defaultDomainData.Name } process { foreach ($identity in $Name) { if ($identity -in $principalsToIgnore) { continue } Write-Verbose "[Resolve-ADPrincipal] Resolving $identity" #region Resolve Principal Domain $domainFQDN = $defaultDomainFQDN $domainName = $defaultDomainName if ($identity -like "*@*") { $domainObject = Get-DomainData -Domain $identity.Split("@")[1] if ($domainObject) { $domainFQDN = $domainObject.Fqdn $domainName = $domainObject.Name } } elseif ($identity -as [System.Security.Principal.SecurityIdentifier]) { if (([System.Security.Principal.SecurityIdentifier]$identity).AccountDomainSid) { $domainObject = Get-DomainData -Domain ([System.Security.Principal.SecurityIdentifier]$identity).AccountDomainSid if ($domainObject) { $domainFQDN = $domainObject.Fqdn $domainName = $domainObject.Name } } } elseif ($identity -like "*\*") { try { $domainObject = Get-DomainData -Domain $identity.Split("\")[0] -ErrorAction Stop } catch { } if ($domainObject) { $domainFQDN = $domainObject.Fqdn $domainName = $domainObject.Name } } $rootDomain = (Get-ADForest -Server $domainFQDN).RootDomain #endregion Resolve Principal Domain if (-not $script:principals[$domainFQDN]) { $script:principals[$domainFQDN] = @{ } } # Return form Cache if available if ($script:principals[$domainFQDN][$identity]) { return $script:principals[$domainFQDN][$identity] } #region Resolve User in AD if ($identity -as [System.Security.Principal.SecurityIdentifier]) { $adObject = Get-ADObject -Server $domainFQDN -LDAPFilter "(objectSID=$identity)" -Properties ObjectSID, SamAccountName # Handle Builtin SIDs that only exist in the root domain if (-not $adObject) { $adObject = Get-ADObject -Server $rootDomain -LDAPFilter "(objectSID=$identity)" -Properties ObjectSID, SamAccountName } } elseif (Test-IsDistinguishedName -Name $identity) { $adObject = Get-ADObject -Server ($identity | ConvertTo-DnsDomainName) -Identity $identity -Properties ObjectSID, SamAccountName } elseif ($identity -like "*\*") { try { $sidName = ([System.Security.Principal.NTAccount]$identity).Translate([System.Security.Principal.SecurityIdentifier]) } catch { Write-Warning "Failed to translate identity: $identity" continue } try { $adObject = Get-ADObject -Server $domainFQDN -LDAPFilter "(objectSID=$sidName)" -Properties ObjectSID, SamAccountName -ErrorAction Stop } catch { } if (-not $adObject) { $script:principals[$domainFQDN][$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[$domainFQDN][$identity] continue } } else { try { $sidName = ([System.Security.Principal.NTAccount]$identity).Translate([System.Security.Principal.SecurityIdentifier]) if ($sidName.Value -like 'S-1-3-*') { $script:principals[$domainFQDN][$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[$domainFQDN][$identity] continue } $adObject = Get-ADObject -Server $domainFQDN -LDAPFilter "(objectSID=$sidName)" -Properties ObjectSID, SamAccountName } catch { $adObject = Get-ADObject -Server $domainFQDN -LDAPFilter "(SamAccountName=$identity)" -Properties ObjectSID, SamAccountName } } if (-not $adObject -or -not $adObject.ObjectSID) { Write-Warning "Failed to resolve principal: $identity" continue } #endregion Resolve User in AD $script:principals[$domainFQDN][$identity] = [pscustomobject]@{ DistinguishedName = $adObject.DistinguishedName Name = $adObject.SamAccountName 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[$domainFQDN][$identity] } } } function Resolve-DomainMapping { <# .SYNOPSIS Resolves a source domain from a GPO export into domain of the destination domain. .DESCRIPTION Resolves a source domain from a GPO export into domain of the destination domain. The mapping data for this is managed by Register-GptDomainMapping. Usual source of mapping data is Import-GptDomainData and a scan of the destination forest. Accepts SID, Fqdn and Netbios Name as input to find the correct domain. Uses SID first, then Fqdn and only as a last resort the Netbios name, if all are specified. It returns an AD Domain object, representing the destination domain the source domain maps to. This object can be faked by the user, if manual data sources need to be included, but it is assumed, that such an object will also have all the data fields required. .PARAMETER DomainSid SID of the domain from the export source. .PARAMETER DomainFqdn Fqdn of the domain from the export source. .PARAMETER DomainName Name of the domain from the export source. .EXAMPLE PS C:\> Resolve-DomainMapping -DomainSid $identity.DomainSID -DomainFqdn $identity.DomainFqdn -DomainName $identity.DomainName Resolves the destination domain to map the specified identity to. Tries to use SID first, then FQDN and Netbios name only if nothing else worked. #> [CmdletBinding()] param ( [string] $DomainSid, [string] $DomainFqdn, [string] $DomainName ) if (-not $script:domainMapping) { throw "No domain mappings loaded yet. Run Import-GptDomainData or Register-GptDomainMapping to initialize the domain resolution table." } if ($DomainSid -and $script:domainMapping.Sid[$DomainSid]) { return $script:domainMapping.Sid[$DomainSid] } if ($DomainFqdn -and $script:domainMapping.FQDN[$DomainFqdn]) { return $script:domainMapping.FQDN[$DomainFqdn] } if ($DomainName -and $script:domainMapping.Name[$DomainName]) { return $script:domainMapping.Name[$DomainName] } throw "No matching domain found! ($DomainSid | $DomainFqdn | $DomainName)" } 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 Update-NetworkDrive { <# .SYNOPSIS Remaps mapped network drives if needed. .DESCRIPTION Remaps mapped network drives if needed. Performs no operation, if no network drives are mapped on a GPO. Migration tables do not correctly update mapped drives, unfoortunately. Requires valid source data to be already imported, for example by running Import-GptDomainData or Import-GptIdentity. .PARAMETER GpoName Name of the GPO to update. .PARAMETER Domain The destination domain into which the GPO has been imported. .EXAMPLE PS C:\> Update-NetworkDrive -GpoName 'Share Y:' -Domain 'contoso.com' Updates the GPO "Share Y:" for the domain contoso.com, remapping the share from the source domain to the destination domain. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $GpoName, [Parameter(Mandatory = $true)] [string] $Domain ) begin { try { $gpoObject = Get-GPO -Domain $Domain -Name $GpoName -ErrorAction Stop $destinationDomain = (Get-DomainData -Domain $Domain).ADObject $gpoADObject = Get-ADObject -Server $destinationDomain.PDCEmulator -Identity $gpoObject.Path -Properties gPCFileSysPath -ErrorAction Stop } catch { throw } 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!" } } process { Write-Verbose "$GpoName : Processing Network Shares" $driveXmlPath = Join-Path -Path $gpoADObject.gPCFileSysPath -ChildPath 'User\Preferences\Drives\Drives.xml' if (-not (Test-Path -Path $driveXmlPath)) { Write-Verbose "$GpoName : Does not contain Network Shares" return } try { $driveString = Get-Content -Path $driveXmlPath -Raw -ErrorAction Stop -Encoding UTF8 } catch { Write-Verbose "$GpoName : Could not access Network Shares file" return } $driveStringNew = $driveString.Replace("\\$sourceDomainDNS\", "\\$($destinationDomain.DNSRoot)\").Replace("\\$sourceDomainNetBios\", "\\$($destinationDomain.NetBIOSName)\") if ($driveStringNew -eq $driveString) { Write-Verbose "$GpoName : Nothing to remap in the defined shares" return } try { Set-Content -Value $driveStringNew -Path $driveXmlPath -Encoding UTF8 -ErrorAction Stop } catch { throw } } } 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 Write-Verbose "Resolved output path to: $resolvedPath" $gpoObjects = @() } process { Write-Verbose "Resolving GPOs to process" if (-not $GpoObject) { $gpoObjects = Get-GPO -All -Domain $Domain | Where-Object DisplayName -Like $Name } else { foreach ($object in $GpoObject) { $gpoObjects += $object } } } end { Write-Verbose "Exporting GPO Objects" $gpoObjects | Export-GptObject -Path $policyFolder.FullName -Domain $Domain Write-Verbose "Exporting GP Links" Export-GptLink -Path $resolvedPath -Domain $Domain Write-Verbose "Exporting GP Permissions" $gpoObjects | Export-GptPermission -Path $resolvedPath -Domain $Domain Write-Verbose "Exporting WMI Filters" $gpoObjects | Export-GptWmiFilter -Path $resolvedPath -Domain $Domain Write-Verbose "Exporting Identities" Export-GptIdentity -Path $resolvedPath -Domain $Domain -Name $Identity -GpoObject $gpoObjects Write-Verbose "Exporting Domain Information" 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 $sourceDomain = [pscustomobject]@{ Domain = $Domain DomainDNSName = $domainObject.DNSRoot NetBIOSName = $domainObject.NetBIOSName BackupVersion = '1.0.0' Timestamp = (Get-Date) DomainSID = $domainObject.DomainSID.Value } $forestObject = Get-ADForest -Server $Domain $domains = $forestObject.Domains | Foreach-Object { Get-ADDomain -Server $_ -Identity $_ } | ForEach-Object { [PSCustomObject]@{ DistinguishedName = $_.DistinguishedName Name = $_.Name SID = $_.DomainSID Fqdn = $_.DNSRoot ADObject = $_ IsTarget = $_.DomainSID -eq $sourceDomain.DomainSID IsRootDomain = $_.DNSRoot -eq $forestObject.RootDomain } } [PSCustomObject]@{ SourceDomain = $sourceDomain ForestDomains = $domains } | 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. .PARAMETER GpoName The name filter pattern of the GPOs to parse for relevant identities export. .PARAMETER GpoObject Specific GPO object to parse for relevant identities to export. .EXAMPLE PS C:\> Export-GptIdentity -Path '.' Export the builtin accounts into the current folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [string[]] $Name, [string[]] $GpoName = '*', [Parameter(ValueFromPipeline = $true)] $GpoObject, [string] $Domain = $env:USERDNSDOMAIN ) begin { $pdcEmulator = (Get-ADDomain -Server $Domain).PDCEmulator $rootDomain = Get-ADDomain (Get-ADForest -Server $Domain).RootDomain [System.Collections.ArrayList]$identities = @() #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 = '500', '501', '502', '512', '513', '514', '515', '516', '517','520', '521', '522', '525', '526', '553', '571', '572' $builtInForestRID = @( '498' # Enterprise Read-only Domain Controllers '518' # Schema Admins '519' # Enterprise Admins '527' # Enterprise Key Admins ) $domainSID = (Get-ADDomain -Server $pdcEmulator).DomainSID.Value $rootDomainSID = $rootDomain.DomainSID.Value $identities.AddRange(($builtInSID | Resolve-ADPrincipal -Domain $Domain)) $identities.AddRange(($builtInRID | Resolve-ADPrincipal -Domain $Domain -Name { '{0}-{1}' -f $domainSID, $_ })) $identities.AddRange(($builtInForestRID | Resolve-ADPrincipal -Domain $rootDomain.DNSRoot -Name { '{0}-{1}' -f $rootDomainSID, $_ })) #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 } process { #region Process GPO-Required Accounts foreach ($gpoItem in $GpoObject) { foreach ($principal in (Get-GptPrincipal -Name $GpoName -GpoObject $GpoObject -Domain $Domain)) { $null = $identities.Add($principal) } } #endregion Process GPO-Required 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) { # Skip empty lines if (-not $link) { continue } $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. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [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, @{ Name = 'WmiFilter'; Expression = { $_.WmiFilter.Name }} | 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. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [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 } } $select_DomainFqdn = @{ name = 'DomainFqdn'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).DomainFqdn } } $select_DomainName = @{ name = 'DomainName'; expression = { (Resolve-ADPrincipal -Name $_.IdentityReference -Domain $Domain).DomainName } } [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, $select_DomainFqdn, $select_DomainName } 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. By default, all filters are exported. Use -ConstrainExport parameter to switch this behavior to: WMI Filters to export are picked up by the GPO they are assigned to. Unassigned filters are ignored. .PARAMETER Path The path where to create the export. Must be an existing folder. .PARAMETER ConstrainExport Don't export all WMI filters, instead: WMI Filters to export are picked up by the GPO they are assigned to. Unassigned filters are ignored. .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, [switch] $ConstrainExport, [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 { if (-not $ConstrainExport) { return } $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 { if ($ConstrainExport) { $foundFilterHash.Values | Where-Object { $_ } | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_wmifilters_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } else { $allFilterHash.Values | Where-Object { $_ } | Export-Csv -Path (Join-Path -Path $Path -ChildPath "gp_wmifilters_$($Domain).csv") -Encoding UTF8 -NoTypeInformation } } } function Get-GptPrincipal { <# .SYNOPSIS Generates a list of principals relevant to the specified GPO. .DESCRIPTION Generates a list of principals relevant to the specified GPO. This is used internally to generate the identities export. It can also be used directly, to assess needed identities (for example when setting up a test domain). .PARAMETER Path Path to an already existing GPO backup. Using this will have the module scan a backup, rather than live GPO. .PARAMETER Name The name to filter GPOs by. Defaults to '*' Accepts multiple strings, a single wildcard match is needed for a GPO to be selected. .PARAMETER GpoObject The GPO to process, as returned by Get-Gpo. .PARAMETER Domain The domain to connect to. Defaults to the user dns domain. .PARAMETER IncludeUNC By default, UNC paths are not included in the output. These too can be read from GPO and might be relevant. .EXAMPLE PS C:\> Get-GptPrincipal Returns the relevant principals from all GPOs in the current domain. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectUsageOfAssignmentOperator', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(DefaultParameterSetName = 'GPO')] param ( [Parameter(ParameterSetName = "Path")] [ValidateScript({ Test-Path -Path $_ })] [string] $Path, [Parameter(ParameterSetName = 'GPO')] [string[]] $Name = '*', [Parameter(ParameterSetName = 'GPO', ValueFromPipeline = $true)] $GpoObject, [string] $Domain = $env:USERDNSDOMAIN, [switch] $IncludeUNC ) begin { if (-not $Path) { $tempPath = New-Item -Path $env:TEMP -ItemType Directory -Name "Gpo_TempBackup_$(Get-Random -Maximum 999999 -Minimum 100000)" -Force $backupPath = $tempPath.FullName } else { $backupPath = (Resolve-Path -Path $Path).ProviderPath } $entryType = @{ 0 = 'User' 1 = 'Computer' 2 = 'LocalGroup' 3 = 'DomainGroup' 4 = 'UniversalGroup' 5 = 'UNCPath' 6 = 'Unknown' } } process { #region Export GPO to temporary path if (-not $Path) { $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 $backupPath } #endregion Export GPO to temporary path } end { $groupPolicyManager = New-Object -ComObject GPMgmt.GPM $migrationTable = $groupPolicyManager.CreateMigrationTable() $constants = $groupPolicyManager.getConstants() $backupDirectory = $groupPolicyManager.GetBackupDir($backupPath) $backupList = $backupDirectory.SearchBackups($groupPolicyManager.CreateSearchCriteria()) foreach ($policyBackup in $backupList) { $migrationTable.Add(0, $policyBackup) $migrationTable.Add($constants.ProcessSecurity, $policyBackup) } foreach ($entry in $migrationTable.GetEntries()) { $paramAddMember = @{ MemberType = 'NoteProperty' Name = 'EntryType' Value = $entryType[$entry.EntryType] PassThru = $true Force = $true } switch ($entry.EntryType) { $constants.EntryTypeUNCPath { if (-not $IncludeUNC) { break } [PSCustomObject]@{ EntryType = $entryType[$entry.EntryType] Path = $entry.Source } } default { #region SID if ($sid = $entry.Source -as [System.Security.Principal.SecurityIdentifier]) { if ($sid.DomainSID) { Resolve-ADPrincipal -Name $sid -Domain $sid.DomainSID | Add-Member @paramAddMember continue } Resolve-ADPrincipal -Name $sid -Domain $Domain | Add-Member @paramAddMember continue } #endregion SID #region Name try { $sid = ([System.Security.Principal.NTAccount]$entry.Source).Translate([System.Security.Principal.SecurityIdentifier]) if ($sid.DomainSID) { Resolve-ADPrincipal -Name $sid -Domain $sid.DomainSID | Add-Member @paramAddMember continue } Resolve-ADPrincipal -Name $sid -Domain $Domain | Add-Member @paramAddMember continue } catch { if ($entry.Source -like '*@*') { $entity, $domainName = $entry.Source -split '@' Resolve-ADPrincipal -Name $entity -Domain $domainName | Add-Member @paramAddMember continue } else { Resolve-ADPrincipal -Name $entry.Source -Domain $Domain | Add-Member @paramAddMember continue } } #endregion Name } } } if (-not $Path) { Remove-Item -Path $tempPath -Recurse -Force } } } function Import-GptDomainData { <# .SYNOPSIS Imports domain information of the source domain. .DESCRIPTION Imports domain information of the source domain. Also responsible for mapping domains from the source forest to the destination forest. .PARAMETER Path The path to the file or the folder it resides in. .PARAMETER Domain The domain into which to import. Used for automatically calculating domain mappings. .EXAMPLE PS C:\> Import-GptDomainData -Path '.' Import the domain information file from the current folder. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $Path, [string] $Domain = $env:USERDNSDOMAIN ) 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 { $domainImport = Import-Clixml $resolvedPath $script:sourceDomainData = $domainImport.SourceDomain $forestObject = Get-ADForest -Server $Domain $targetDomain = Get-ADDomain -Server $Domain $domains = $forestObject.Domains | Foreach-Object { Get-ADDomain -Server $_ -Identity $_ } | ForEach-Object { [PSCustomObject]@{ DistinguishedName = $_.DistinguishedName Name = $_.Name SID = $_.DomainSID Fqdn = $_.DNSRoot ADObject = $_ IsTarget = $_.DomainSID -eq $targetDomain.DomainSID IsRootDomain = $_.DNSRoot -eq $forestObject.RootDomain } } foreach ($domainItem in $domains) { foreach ($sourceDomainEntry in $domainImport.ForestDomains) { if ($sourceDomainEntry.Name -eq $domainItem.Name) { Register-GptDomainMapping -SourceName $sourceDomainEntry.Name -SourceFQDN $sourceDomainEntry.Fqdn -SourceSID $sourceDomainEntry.SID -Destination $domainItem.ADObject } } } foreach ($domainItem in $domains) { foreach ($sourceDomainEntry in $domainImport.ForestDomains) { if ($sourceDomainEntry.Fqdn -eq $domainItem.Fqdn) { Register-GptDomainMapping -SourceName $sourceDomainEntry.Name -SourceFQDN $sourceDomainEntry.Fqdn -SourceSID $sourceDomainEntry.SID -Destination $domainItem.ADObject } } } foreach ($domainItem in $domains) { foreach ($sourceDomainEntry in $domainImport.ForestDomains) { if ($sourceDomainEntry.SID -eq $domainItem.SID) { Register-GptDomainMapping -SourceName $sourceDomainEntry.Name -SourceFQDN $sourceDomainEntry.Fqdn -SourceSID $sourceDomainEntry.SID -Destination $domainItem.ADObject } } } $sourceDomain = $domainImport.ForestDomains | Where-Object IsTarget $sourceForestRootDomain = $domainImport.ForestDomains | Where-Object IsRootDomain foreach ($domainItem in $domains) { if ($domainItem.IsRootDomain) { Register-GptDomainMapping -SourceName $sourceForestRootDomain.Name -SourceFQDN $sourceForestRootDomain.Fqdn -SourceSID $sourceForestRootDomain.SID -Destination $domainItem.ADObject } } foreach ($domainItem in $domains) { if ($domainItem.IsTarget) { Register-GptDomainMapping -SourceName $sourceDomain.Name -SourceFQDN $sourceDomain.Fqdn -SourceSID $sourceDomain.SID -Destination $domainItem.ADObject } } } } 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)" } $rootDomain = (Get-ADForest -Server $Domain).RootDomain # 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 } } $select_TargetDomain = @{ Name = 'TargetDomain' Expression = { $domainObject } } } 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 { $adObject = Get-ADObject -Server $rootDomain -LDAPFilter "(objectSID=$($importEntry.SID))" -Properties Name if (-not $adObject) { Write-Warning "Failed to translate identity: $($importEntry.Name) ($($importEntry.SID))" continue } $targetName = $adObject.Name } $script:identityMapping.Add(($importEntry | Select-Object *, $select_TargetName)) } #endregion Case: Native BuiltIn Principal #region Case: Domain Specific BuiltIn Principal elseif ($importEntry.IsBuiltIn -eq 'True') { try { $domainObject = Resolve-DomainMapping -DomainSid ($importEntry.SID -as [System.Security.Principal.SecurityIdentifier]).AccountDomainSid.Value -DomainFqdn $importEntry.DomainFqdn -DomainName $importEntry.DomainName } catch { throw "Cannot resolve domain $($importEntry.DomainFqdn) for $($importEntry.Group) $($importEntry.Name)! $_" } $targetSID = '{0}-{1}' -f $domainObject.DomainSID, $importEntry.RID $adObject = Get-ADObject -Server $domainObject.DNSRoot -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, $select_TargetDomain)) } #endregion Case: Domain Specific BuiltIn Principal #region Case: Custom Principal else { try { $domainObject = Resolve-DomainMapping -DomainSid ($importEntry.SID -as [System.Security.Principal.SecurityIdentifier]).AccountDomainSid.Value -DomainFqdn $importEntry.DomainFqdn -DomainName $importEntry.DomainName } catch { throw "Cannot resolve domain $($importEntry.DomainFqdn) for $($importEntry.Group) $($importEntry.Name)! $_" } $adObject = Get-ADObject -Server $domainObject.DNSRoot -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, $select_TargetDomain)) } #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. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [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 { if ($_.Exception.InnerException.HResult -eq 0x800700B7) { New-ImportResult -Action 'Importing Group Policy Links' -Step 'Applying Link: Already Exists' -Target $linkItem.GpoName -Data $linkItem -Success $true -ErrorData $_ } else { 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 } # Mapped network drives are not correctly covered by Migration Tables Update-NetworkDrive -GpoName $gpoEntry.DisplayName -Domain $Domain 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. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [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 { New-ImportResult -Action 'Update Gpo Permission' -Step 'Apply File Permission' -Target $Permission.GpoName -Success $false -Data $Permission, $accessRule -ErrorData $_ continue } #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-GptIdentity 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 Register-GptDomainMapping { <# .SYNOPSIS Maps source domain names to the associated target domain. .DESCRIPTION Maps source domain names to the associated target domain. This is used to map source identities to the correct destination domains. This data is used during import/restore only! .PARAMETER SourceName Netbios name of the source domain. Last resort for source identity domain translation. .PARAMETER SourceFQDN FQDN of the source domain. .PARAMETER SourceSID SID of the source domain. Primary tool for source identity domain translation. .PARAMETER Destination The destination domain. Either offer an active directory domain object (Returned by Get-ADDOmain) or a name that will be looked up. .PARAMETER Server Server to use for looking up the destination domain data. Used only when the Destination parameter waas set to string value (such as the fqdn of the domain). .EXAMPLE PS C:\> Register-GptDomainMapping -SourceName corp -SourceFQDN corp.contoso.com -SourceSID $sid -Destination $domain Registers name mappings, pointing corp.contoso.com to the destination domain stored in $domain. #> [CmdletBinding()] param ( [string] $SourceName, [string] $SourceFQDN, [string] $SourceSID, $Destination, [string] $Server ) begin { if (-not $script:domainMapping) { $script:domainMapping = @{ Name = @{ } FQDN = @{ } SID = @{ } } } # Do not check for actual type, in order to allow users to fake/mock up a custom object if ($Destination.PSObject.TypeNames -contains 'Microsoft.ActiveDirectory.Management.ADDomain') { $domainObject = $Destination } else { $params = @{ Domain = $Destination ErrorAction = 'Stop' } if ($Server) { $params['Server'] = $Server } try { $domainObject = Get-ADDomain @params } catch { Write-Warning "Failed to resolve destination domain: $Destination : $_" throw } } } process { if ($SourceName) { $script:domainMapping.Name[$SourceName] = $domainObject } if ($SourceFQDN) { $script:domainMapping.FQDN[$SourceFQDN] = $domainObject } if ($SourceSID) { $script:domainMapping.SID[$SourceSID] = $domainObject } } } 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 @common } process { Write-Verbose "Importing Identities" Import-GptIdentity @common -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 } } #endregion Load compiled code |