ADMirror.psm1
################################# #FUNCTION 1: Dump-ADOuStructure ################################# function Dump-ADOuStructure { ########################################################################################################## <# .SYNOPSIS Dumps the OU hierarchy of a domain to an XML file. .DESCRIPTION Creates a date and time named XML backup of a domain's OU structure. Intended to be used with a sister function that can mirror the dumped OU structure to a test domain. .EXAMPLE Dump-ADOuStructure -Domain halo.net Dumps the OU hierarchy of the target domain, halo.net, to a date and time stamped XML file. .OUTPUTS Date and time stamped xml file, e.g. 150410093716_HALO_OU_Dump.xml .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################### ## Function Options and Parameters ################################### #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory,Position=1)] [ValidateScript({Get-ADDomain $_})] [String]$Domain ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ######## ## Main ######## #Craete a variable for the domain DN $DomainDn = (Get-ADDomain -Identity $Domain).DistinguishedName #Craete a variable for the domain DN $DomainNetbios = (Get-ADDomain -Identity $Domain).NetBIOSName #Specify a XML report variable $CsvReport = ".\$(Get-Date -Format yyMMddHHmmss)_$($DomainNetbios)_OU_Dump.xml" #Create an array to contain our custom PS objects $TotalOus = @() #Create user counter $i = 0 #Get-ADOrganizationalUnit dumps the OU structure in a logical order (thank you cmdlet author!) $Ous = Get-ADOrganizationalUnit -Filter * -SearchScope Subtree -Server $Domain -Properties ParentGuid -ErrorAction SilentlyContinue | Select Name,DistinguishedName,ParentGuid #Check that we have some output if ($Ous) { #Loop through each OU, create a custom object and add to $TotalOUs foreach ($Ou in $Ous){ #Convert the parentGUID attribute (stored as a byte array) into a proper-job GUID $ParentGuid = ([GUID]$Ou.ParentGuid).Guid #Attempt to retrieve the object referenced by the parent GUID $ParentObject = Get-ADObject -Identity $ParentGuid -Server $Domain -ErrorAction SilentlyContinue #Check that we've retrieved the parent if ($ParentObject) { #Create a custom PS object $OuInfo = [PSCustomObject]@{ Name = $Ou.Name DistinguishedName = $Ou.DistinguishedName ParentDn = $ParentObject.DistinguishedName DomainDn = $DomainDn } #End of $Properties... #Add the object to our array $TotalOus += $OuInfo #Spin up a progress bar for each filter processed Write-Progress -Activity "Finding OUs in $DomainDn" -Status "Processed: $i" -PercentComplete -1 #Increment the filter counter $i++ } #End of if ($ParentObject) } #End of foreach ($Ou in $Ous) #Dump custom OU info to XML file Export-Clixml -Path $CsvReport -InputObject $TotalOus #Message to screen Write-Host "OU information dumped to $CSVReport" } #End of if ($Ous) Else { #Write message to screen Write-Error -Message "Failed to retrieve OU information." } #End of else ($Ous) } #end of function Dump-ADOuStructure ################################### #FUNCTION 2: Mirror-ADOuStructure ################################### function Mirror-ADOuStructure { ########################################################################################################## <# .SYNOPSIS Mirrors an XML dump of a source domain's OU hierarchy to a target test domain. .DESCRIPTION Creates the OU structure contained in a backup XML file in a target domain. Does not create OUs if they already exist. Intended to be used with a sister function that dumps the OU structure from a source domain. Logs all function actions to a date and time named log. Requirements: * PowerShell ActiveDirectory Module * An XML backup created by partner Dump-ADOuStructure function * Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) to view script log NB - there will be an error written to screen following the test for the existence of an OU. This may result in a lot of red text. .EXAMPLE Mirror-ADOuStructure -Domain contoso.com -BackupXml .\150410093716_HALO_OU_Dump.xml Creates the OU structure contained in the 150410093716_HALO_OU_Dump.xml backup file in the contoso.com domain. Does not create OUs if they already exist. Writes a log file of all function actions. .OUTPUTS Date and time stamped log file, e.g. 150410110533_AD_OU_Mirror.log, for use with Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ EXIT CODES: 1 - Report file not found 2 - Custom XML OU file not found .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################### ## Function Options and Parameters ################################### #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory=$True,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #The source backup file [parameter(Mandatory=$True,Position=2)] [ValidateScript({Test-Path -Path $_})] [String]$BackupXml ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ############################## ## FUNCTION - Log-ScriptEvent ############################## <# Write a line of data to a script log file in a format that can be parsed by Trace32.exe / CMTrace.exe The severity of the logged line can be set as: 1 - Information 2 - Warning 3 - Error Warnings will be highlighted in yellow. Errors are highlighted in red. The tools: SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ #> Function Log-ScriptEvent { #Define and validate parameters [CmdletBinding()] Param( #Path to the log file [parameter(Mandatory=$True)] [String]$NewLog, #The information to log [parameter(Mandatory=$True)] [String]$Value, #The source of the error [parameter(Mandatory=$True)] [String]$Component, #The severity (1 - Information, 2- Warning, 3 - Error) [parameter(Mandatory=$True)] [ValidateRange(1,3)] [Single]$Severity ) #Obtain UTC offset $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime $DateTime.SetVarDate($(Get-Date)) $UtcValue = $DateTime.Value $UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21) #Create the line to be logged $LogLine = "<![LOG[$Value]LOG]!>" +` "<time=`"$(Get-Date -Format HH:mm:ss.fff)$($UtcOffset)`" " +` "date=`"$(Get-Date -Format M-d-yyyy)`" " +` "component=`"$Component`" " +` "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +` "type=`"$Severity`" " +` "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +` "file=`"`">" #Write the line to the passed log file Add-Content -Path $NewLog -Value $LogLine } #End of Function Log-ScriptEvent ########################################################################################################## ######## ## Main ######## #Create a variable to represent a new script log, constructing the report name from date details $NewReport = ".\$(Get-Date -Format yyMMddHHmmss)_AD_OU_Mirror.log" #Make sure the script log has been created if (New-Item -ItemType File -Path $NewReport) { ##Start writing to the script log (Start_Script) Log-ScriptEvent $NewReport ("=" * 90) "Start-Script" 1 Log-ScriptEvent $NewReport "TARGET_DOMAIN: $Domain" "Start_Script" 1 Log-ScriptEvent $NewReport "BACKUP_SOURCE: $BackupXml" "Start_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Start_Script" 1 Log-ScriptEvent $NewReport " " " " 1 #Instantiate an object for the target domain $TargetDomain = Get-ADDomain -Identity $Domain #Obtain the target domain FQDN $TargetDomainFqdn = $TargetDomain.DNSRoot #Obtain the target domain DN $TargetDomainDn = $TargetDomain.DistinguishedName #Obtain the target domain PDCe $TargetPdc = $TargetDomain.PDCEmulator #Import the OU information contained in the XML file $OuInfo = Import-Clixml -Path $BackupXml -ErrorAction SilentlyContinue #Make sure we have custom OU info if ($OuInfo) { #Log custom XML import success Log-ScriptEvent $NewReport "Custom OU objects successfully imported from $BackupXml" "Import_OUs" 1 Log-ScriptEvent $NewReport " " " " 1 #Obtain the source domain DN from the first custom OU object $SourceDomainDn = ($OuInfo | Select -First 1).DomainDn #Create a counter $i = 0 #Loop through each of the OUs foreach ($Ou in $OuInfo) { #Replace the domain DN with the target filter DN for our OU $TargetOuDn = $Ou.DistinguishedName �Replace $SourceDomainDn,$TargetDomainDn #Replace the domain DN with the target filter DN for our parent path $TargetParentDn = $Ou.ParentDn �Replace $SourceDomainDn,$TargetDomainDn #Test that the parent exists Try {$TargetParent = Get-ADObject -Identity $TargetParentDn -Server $TargetPdc} Catch{} #Check to see that the parent OU already exists if ($TargetParent) { #Log that object exists Log-ScriptEvent $NewReport "`"$TargetParentDn`" parent already exists in $Domain - checking for child OU..." "Import_OUs" 1 #Test that the OU doesn't already exist Try {$TargetOu= Get-ADObject -Identity $TargetOuDn -Server $TargetPdc} Catch {} #Check to see if the target OU already exists if ($TargetOu) { #Log that object exists Log-ScriptEvent $NewReport "`"$TargetOuDn`" already exists in $Domain" "Import_OUs" 1 Log-ScriptEvent $NewReport " " " " 1 } #End of if ($TargetOu) else { #Log that object does not exist Log-ScriptEvent $NewReport "`"$TargetOuDn`" does not exist in $Domain - attempting to create OU..." "Import_OUs" 1 #Create the OU $NewOu = New-ADOrganizationalUnit -Name $Ou.Name ` -Path $TargetParentDn ` -Server $TargetPdc ` -ErrorAction SilentlyContinue #Check the success of the New-ADOrganizationalUnit cmdlet if ($?) { #Log success of New-ADOrganizationalUnit cmdlet Log-ScriptEvent $NewReport "Creation of `"$TargetOuDn`" succeeded." "Import_OUs" 1 Log-ScriptEvent $NewReport " " " " 1 } #End of if ($?) else { #Log failure of New-ADOrganizationalUnit cmdlet Log-ScriptEvent $NewReport "Creation of `"$TargetOuDn`" failed. $($Error[0].exception.message)" "Import_OUs" 3 Log-ScriptEvent $NewReport " " " " 1 } #End of else ($?) } #End of else ($TargetOu) } #End of if ($TargetParent) else { #Log that object doesn't exist Log-ScriptEvent $NewReport "$TargetParentDn parent does not exist in $Domain" "Import_OUs" 3 } #End of else ($TargetParent) #Spin up a progress bar for each filter processed Write-Progress -Activity "Importing OUs to $TargetDomainFqdn" -Status "Processed: $i" -PercentComplete -1 #Increment the filter counter $i++ #Nullify key variables $TargetOu = $null $TargetParent = $null } #End of foreach($Ou in $Ous) } #End of if ($OuInfo) else { #Log failure to import custom OU XML object Log-ScriptEvent $NewReport "$BackupXml import failed" "Import_OUs" 3 Log-ScriptEvent $NewReport "Script execution stopped" "Import_OUs" 1 Log-ScriptEvent $NewReport ("=" * 90) "Import_OUs" 1 Write-Error "$BackupXml not found. Script execution stopped." Exit 2 } #End of else ($OuInfo) #Close of the script log Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 Log-ScriptEvent $NewReport "OUs_PROCESSED: $i" "Finish_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 } #End of if (New-Item -ItemType File -Path $NewReport) else { #Write a custom error Write-Error "$NewReport not found. Function execution stopped." Exit 1 } #End of else (New-Item -ItemType File -Path $NewReport) } #end of function Mirror-ADOuStructure ########################### #FUNCTION 3: Dump-ADUsers ########################### function Dump-ADUsers { ########################################################################################################## <# .SYNOPSIS Dumps Users accounts for a domain .DESCRIPTION Creates a date and time named XML backup of a domain's user accounts. Intended to be used with a sister function that can mirror the dumped OU structure to a test domain. .EXAMPLE Dump-ADUsers -Domain halo.net Dumps the user accounts of the target domain, halo.net, to a date and time stamped XML file. .EXAMPLE Dump-ADUsers -Domain halo.net -TargetOu "OU=Test Users,DC=halo,DC=net" Dumps the user accounts of the target OU, "Test Users", and subtree to a date and time stamped XML file. .OUTPUTS Date and time stamped xml file, e.g. 150410093716_HALO_User_Dump.xml .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #Optional target OU [parameter(Position=2)] [ValidateScript({Get-ADOrganizationalUnit -Identity $_})] [String]$TargetOu ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ######## ## Main ######## #Create a variable for the domain DN $DomainDn = (Get-ADDomain -Identity $Domain).DistinguishedName #Create a variable for the domain DN $DomainNetbios = (Get-ADDomain -Identity $Domain).NetBIOSName #Specify a XML report variable $XmlReport = ".\$(Get-Date -Format yyMMddHHmmss)_$($DomainNetbios)_User_Dump.xml" #Create an array to contain our custom PS objects $TotalUsers = @() #Create user counter $i = 0 #Check for target OU if ($TargetOu) { #Create splatted parameters for Get-ADuser command $Parameters = @{ Filter = "*" SearchBase = $TargetOu SearchScope = "SubTree" Server = $Domain ErrorAction = "SilentlyContinue" } #End of $Parameters } #End of if ($TargetOu) else { #Create splatted parameters for Get-ADuser command $Parameters = @{ Filter = "*" SearchScope = "SubTree" Server = $Domain ErrorAction = "SilentlyContinue" } #End of $Parameters } #end of else ($TargetOu) #Get a list of AD users $Users = Get-ADUser @Parameters -Properties mail,ParentGuid,Description,DisplayName if ($Users) { foreach ($User in $Users) { #Convert the parentGUID attribute (stored as a byte array) into a proper-job GUID $ParentGuid = ([GUID]$User.ParentGuid).Guid #Attempt to retrieve the object referenced by the parent GUID $ParentObject = Get-ADObject -Identity $ParentGuid -Server $Domain -ErrorAction SilentlyContinue #Check that we've retrieved the parent if ($ParentObject) { #Create a custom PS object $UserInfo = [PSCustomObject]@{ GivenName = $User.GivenName Surname = $User.Surname Name = $User.Name SamAccountName = $User.SamAccountName DisplayName = $User.DisplayName mail = $User.mail Description = $User.Description UserDn = $User.DistinguishedName ParentDn = $ParentObject.DistinguishedName DomainDn = $DomainDn } #End of $UserInfo... #Add the object to our array $TotalUsers += $UserInfo #Spin up a progress bar for each filter processed Write-Progress -Activity "Finding users in $DomainDn" -Status "Processed: $i" -PercentComplete -1 #Increment the filter counter $i++ } #end of if ($ParentObject) } #end of foreach ($User in $Users) } #end if ($Users) #Dump custom User info to XML file Export-Clixml -Path $XmlReport -InputObject $TotalUsers #Message to screen Write-Host "User information dumped to $XmlReport" } #end of function Dump-ADUsers ############################# #FUNCTION 4: Mirror-ADUsers ############################# function Mirror-ADUsers { ########################################################################################################## <# .SYNOPSIS Mirrors an XML dump of a source domain's user accounts to a target test domain. .DESCRIPTION Creates user accounts contained in a backup XML file in a target domain. Does not create users if they already exist. Does not create users if the parent OU does not already exist. Intended to be used with a sister function that dumps the user accounts from a source domain. Logs all function actions to a date and time named log. Requirements: * PowerShell ActiveDirectory Module * An XML backup created by partner Dump-ADUsers function * Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) to view script log .EXAMPLE Mirror-ADUsers -Domain contoso.com -BackupXml .\150410093716_HALO_User_Dump.xml Creates the user accounts contained in the 150410093716_HALO_User_Dump.xml backup file in the contoso.com domain. Does not create users if they already exist. Does not create users if the parent OU does not already exist. Writes a log file of all function actions. .EXAMPLE Mirror-ADUsers -Domain contoso.com -BackupXml .\150410093716_HALO_User_Dump.xml -TargetOu "OU=Test Users,DC=Halo,DC=Net" Creates the user accounts contained in the 150410093716_HALO_OU_Dump.xml backup file in the contoso.com domain. Creates Users in the 'Test Users' OU. Does not create users if they already exist. Does not create users if the target OU does not exist. Writes a log file of all functions actions. .OUTPUTS Date and time stamped log file, e.g. 150410110533_AD_User_Mirror.log, for use with Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ EXIT CODES: 1 - Report file not found 2 - Custom XML User file not found .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory=$True,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #The source backup file [parameter(Mandatory=$True,Position=2)] [ValidateScript({Test-Path -Path $_})] [String]$BackupXml, #Optional target OU [parameter(Position=3)] [ValidateScript({Get-ADOrganizationalUnit -Identity $_})] [String]$TargetOu ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ############################## ## FUNCTION - Log-ScriptEvent ############################## <# Write a line of data to a script log file in a format that can be parsed by Trace32.exe / CMTrace.exe The severity of the logged line can be set as: 1 - Information 2 - Warning 3 - Error Warnings will be highlighted in yellow. Errors are highlighted in red. The tools: SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ #> Function Log-ScriptEvent { #Define and validate parameters [CmdletBinding()] Param( #Path to the log file [parameter(Mandatory=$True)] [String]$NewLog, #The information to log [parameter(Mandatory=$True)] [String]$Value, #The source of the error [parameter(Mandatory=$True)] [String]$Component, #The severity (1 - Information, 2- Warning, 3 - Error) [parameter(Mandatory=$True)] [ValidateRange(1,3)] [Single]$Severity ) #Obtain UTC offset $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime $DateTime.SetVarDate($(Get-Date)) $UtcValue = $DateTime.Value $UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21) #Create the line to be logged $LogLine = "<![LOG[$Value]LOG]!>" +` "<time=`"$(Get-Date -Format HH:mm:ss.fff)$($UtcOffset)`" " +` "date=`"$(Get-Date -Format M-d-yyyy)`" " +` "component=`"$Component`" " +` "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +` "type=`"$Severity`" " +` "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +` "file=`"`">" #Write the line to the passed log file Add-Content -Path $NewLog -Value $LogLine } #End of Function Log-ScriptEvent ########################################################################################################## ######## ## Main ######## #Create a variable to represent a new script log, constructing the report name from date details $NewReport = ".\$(Get-Date -Format yyMMddHHmmss)_AD_User_Mirror.log" #Make sure the script log has been created if (New-Item -ItemType File -Path $NewReport) { ##Start writing to the script log (Start_Script) Log-ScriptEvent $NewReport ("=" * 90) "Start-Script" 1 Log-ScriptEvent $NewReport "TARGET_DOMAIN: $Domain" "Start_Script" 1 Log-ScriptEvent $NewReport "BACKUP_SOURCE: $BackupXml" "Start_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Start_Script" 1 Log-ScriptEvent $NewReport " " " " 1 #Instantiate an object for the target domain $TargetDomain = Get-ADDomain -Identity $Domain #Obtain the target domain FQDN $TargetDomainFqdn = $TargetDomain.DNSRoot #Obtain the target domain DN $TargetDomainDn = $TargetDomain.DistinguishedName #Import the OU information contained in the XML file $UserInfo= Import-Clixml -Path $BackupXml -ErrorAction SilentlyContinue #Make sure we have custom user info if ($UserInfo) { #Log custom XML import success Log-ScriptEvent $NewReport "Custom User objects successfully imported from $BackupXml" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Obtain the source domain DN from the first custom user object $SourceDomainDn = ($UserInfo| Select -First 1).DomainDn #Create counters $i = 0 # users processed $j = 0 # users matched $k = 0 # user created $l = 0 # BUILTIN matched $m = 0 # user creation failed #Loop through each of the custom user objects foreach ($User in $UserInfo) { #Check for know accounts Switch -Wildcard ($User.SamAccountName) { "Administrator" { #Log that BUILTIN account found Log-ScriptEvent $NewReport "`"$(($User).SamAccountName)`" BUILTIN Administrator account matched in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user processed and BUILTIN matched counters $i++ $l++ } "Guest" { #Log that BUILTIN account found Log-ScriptEvent $NewReport "`"$(($User).SamAccountName)`" BUILTIN Guest account matched in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user processed and BUILTIN matched counters $i++ $l++ } "krbtgt*" { #Log that BUILTIN account found Log-ScriptEvent $NewReport "`"$(($User).SamAccountName)`" BUILTIN krbtgt account matched in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user processed and BUILTIN matched counters $i++ $l++ } "*$" { #Log that BUILTIN account found Log-ScriptEvent $NewReport "`"$(($User).SamAccountName)`" BUILTIN TDO account matched in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user processed and BUILTIN matched counters $i++ $l++ } Default { #Test that the user SamAccountName doesn't already exist try {$TargetUserSAM = Get-ADUser -Identity $User.SamAccountName -Server $TargetDomainFqdn} catch {} #If we have a user then onwards... if ($TargetUserSAM) { #Log that object exists Log-ScriptEvent $NewReport "SamAccountName - `"$(($User).SamAccountName)`" - already exists in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user matched counter $j++ } #End of if ($TargetUserSAM) else { #Log that object does not exist Log-ScriptEvent $NewReport "SamAccountName - `"$(($User).SamAccountName)`" - does not exist in $Domain" "Mirror_Users" 1 #Test that the user Name doesn't already exist try{$TargetUserName = Get-ADUser -Identity $User.Name -Server $TargetDomainFqdn} catch {} #If we have a user then onwards... if ($TargetUserName) { #Log that object exists Log-ScriptEvent $NewReport "User Name - `"$(($User).Name)`" - already exists in $Domain" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user matched counter $j++ } #End of if ($TargetUserName) else { #Log that object does not exist Log-ScriptEvent $NewReport "User Name - `"$(($User).Name)`" - does not exist in $Domain" "Mirror_Users" 1 #Determine where we create the user if ($TargetOu) { #Log that we are using a parameter value as our target OU Log-ScriptEvent $NewReport "Using supplied paramter - $TargetOu - as user parent OU" "Mirror_Users" 1 #Attempt to create user in Target OU $NewUser = New-ADUser -Name $User.Name ` -GivenName $User.GivenName ` -Surname $User.SurName ` -SamAccountName $User.SamAccountName ` -DisplayName $User.DisplayName ` -EmailAddress $User.Mail ` -Description $User.Description ` -Path $TargetOu ` -ErrorAction SilentlyContinue #Check the success of the New-ADUser cmdlet if ($?) { #Log success of New-ADUser cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($User).SamAccountName)`" succeeded." "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user created counter $k++ } #End of if ($?) else { #Log failure of New-ADUser cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($User).SamAccountName)`" failed. $($Error[0].exception.message)" "Mirror_Users" 3 Log-ScriptEvent $NewReport " " " " 1 #Increment user creation failed counter $m++ } #End of else ($?) } #End of if ($TargetOu) else { #Replace the domain DN with the target filter DN for our parent path $TargetParentDn = $User.ParentDn �Replace $SourceDomainDn,$TargetDomainDn #Test that the parent exists Try{$TargetParent = Get-ADObject -Identity $TargetParentDn -Server $TargetDomainFqdn} Catch {} #Check to see that the parent OU already exists if ($TargetParent) { #Log that object exists Log-ScriptEvent $NewReport "`"$TargetParentDn`" parent already exists in $Domain" "Mirror_Users" 1 #Attempt to create user in Parent OU $NewUser = New-ADUser -Name $User.Name ` -GivenName $User.GivenName ` -Surname $User.SurName ` -SamAccountName $User.SamAccountName ` -DisplayName $User.DisplayName ` -EmailAddress $User.Mail ` -Description $User.Description ` -Path $TargetParentDn ` -ErrorAction SilentlyContinue #Check the success of the New-ADUser cmdlet if ($?) { #Log success of New-ADUser cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($User).SamAccountName)`" succeeded." "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 #Increment user created counter $k++ } #End of if ($?) else { #Log failure of New-ADUser cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($User).SamAccountName)`" failed. $($Error[0].exception.message)" "Mirror_Users" 3 Log-ScriptEvent $NewReport " " " " 1 #Increment user creation failed counter $m++ } #End of else ($?) } #End of if ($TargetParent) else { #Log that object does not exist Log-ScriptEvent $NewReport "`"$TargetParentDn`" parent does not exist in $Domain... user creation will not be attempted" "Mirror_Users" 1 Log-ScriptEvent $NewReport " " " " 1 } #End of else ($TargetParent) } #End of else ($TargetOu) } #End of else ($TargetUserName) } #End of else ($TargetUserSAM) #Spin up a progress bar for each filter processed Write-Progress -Activity "Mirroring users to $TargetDomainFqdn" -Status "Processed: $i" -PercentComplete -1 #Increment the user processed counter $i++ #Nullify key variables $TargetUserSAM = $null $TargetUserName = $null $TargetUserDn = $null $TargetParent = $null } #End of Switch Default } #End of Switch -Wildcard ($User.UserDn) } #End of foreach($User in $Users) } #End of if ($UserInfo) else { #Log failure to import custom OU XML object Log-ScriptEvent $NewReport "$BackupXml import failed" "Mirror_Users" 3 Log-ScriptEvent $NewReport "Script execution stopped" "Mirror_Users" 1 Log-ScriptEvent $NewReport ("=" * 90) "Mirror_Users" 1 Write-Error "$BackupXml not found. Script execution stopped." Exit 2 } #End of else ($UserInfo) #Close of the script log Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 Log-ScriptEvent $NewReport "USERS_PROCESSED: $i" "Finish_Script" 1 Log-ScriptEvent $NewReport "ACCOUNTS_MATCHED: $j" "Finish_Script" 1 Log-ScriptEvent $NewReport "ACCOUNTS_CREATED_SUCCESS: $k" "Finish_Script" 1 Log-ScriptEvent $NewReport "ACCOUNTS_CREATED_FAILURE: $m" "Finish_Script" 1 Log-ScriptEvent $NewReport "BUILTIN_ACCOUNTS: $l" "Finish_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 } #End of if (New-Item -ItemType File -Path $NewReport) else { #Write a custom error Write-Error "$NewReport not found. Script execution stopped." Exit 1 } #End of else (New-Item -ItemType File -Path $NewReport) } #end of function Mirror-ADUsers ############################ #FUNCTION 5: Dump-ADGroups ############################ function Dump-ADGroups { ########################################################################################################## <# .SYNOPSIS Dumps groups for a domain .DESCRIPTION Creates a date and time named XML backup of a domain's groups. Intended to be used with a sister script that can mirror the dumped groups to a test domain. .EXAMPLE Dump-ADGroups -Domain halo.net Dumps the groups of the target domain, halo.net, to a date and time stamped XML file. .EXAMPLE Dump-ADGroups -Domain halo.net -TargetOu "OU=Test Groups,DC=halo,DC=net" Dumps the groups of the target OU, "Test Groups", and subtree to a date and time stamped XML file. .OUTPUTS Date and time stamped xml file, e.g. 150410093716_HALO_Group_Dump.xml .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #Optional target OU [parameter(Position=2)] [ValidateScript({Get-ADOrganizationalUnit -Identity $_})] [String]$TargetOu ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ######## ## Main ######## #Create a variable for the domain DN $DomainDn = (Get-ADDomain -Identity $Domain).DistinguishedName #Create a variable for the domain DN $DomainNetbios = (Get-ADDomain -Identity $Domain).NetBIOSName #Specify a XML report variable $XmlReport = ".\$(Get-Date -Format yyMMddHHmmss)_$($DomainNetbios)_Group_Dump.xml" #Create an array to contain our custom PS objects $TotalGroups = @() #Create user counter $i = 0 #Check for target OU if ($TargetOu) { #Create splatted parameters for Get-ADuser command $Parameters = @{ Filter = "*" SearchBase = $TargetOu SearchScope = "SubTree" Server = $Domain ErrorAction = "SilentlyContinue" } #End of $Parameters } #End of if ($TargetOu) else { #Create splatted parameters for Get-ADuser command $Parameters = @{ Filter = "*" SearchScope = "SubTree" Server = $Domain ErrorAction = "SilentlyContinue" } #End of $Parameters } #end of else ($TargetOu) #Get a list of AD users $Groups = Get-ADGroup @Parameters -Properties mail,ParentGuid,Description,DisplayName,members,managedBy if ($Groups) { foreach ($Group in $Groups) { #Convert the parentGUID attribute (stored as a byte array) into a proper-job GUID $ParentGuid = ([GUID]$Group.ParentGuid).Guid #Attempt to retrieve the object referenced by the parent GUID $ParentObject = Get-ADObject -Identity $ParentGuid -Server $Domain -ErrorAction SilentlyContinue #Check that we've retrieved the parent if ($ParentObject) { #Create a custom PS object $GroupInfo = [PSCustomObject]@{ GroupCategory = $Group.GroupCategory GroupScope = $Group.GroupScope Name = $Group.Name SamAccountName = $Group.SamAccountName DisplayName = $Group.DisplayName members = $Group.members managedBy = $Group.managedBy Description = $Group.Description GroupDn = $Group.DistinguishedName ParentDn = $ParentObject.DistinguishedName DomainDn = $DomainDn } #End of $GroupInfo... #Add the object to our array $TotalGroups += $GroupInfo #Spin up a progress bar for each filter processed Write-Progress -Activity "Finding groups in $DomainDn" -Status "Processed: $i" -PercentComplete -1 #Increment the filter counter $i++ } #end of if ($ParentObject) } #end of foreach ($Group in $Groups) } #end if ($Groups) #Dump custom User info to XML file Export-Clixml -Path $XmlReport -InputObject $TotalGroups #Message to screen Write-Host "User information dumped to $XmlReport" } #end of function Dump-ADGroups ############################## #FUNCTION 6: Mirror-ADGroups ############################## function Mirror-ADGroups { ########################################################################################################## <# .SYNOPSIS Mirrors an XML dump of a source domain's groups to a target test domain. .DESCRIPTION Creates groups contained in a backup XML file in a target domain. Does not create groups if they already exist. Does not create groups if the parent OU does not already exist. Populates groups memberships. IMPORTANT: Foreign Security Principals won't be added. Intended to be used with a sister function that dumps the groups from a source domain. Logs all function actions to a date and time named log. Requirements: * PowerShell ActiveDirectory Module * An XML backup created by partner Dump-ADGroups function * Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) to view script log .EXAMPLE Mirror-ADGroups -Domain contoso.com -BackupXml .\150410093716_HALO_Group_Dump.xml Creates the groups contained in the 150410093716_HALO_Group_Dump.xml backup file in the contoso.com domain. Does not create groups if they already exist. Does not create groups if the parent OU does not already exist. Writes a log file of all function actions. .EXAMPLE Mirror-ADGroups -Domain contoso.com -BackupXml .\150410093716_HALO_Group_Dump.xml -TargetOu "OU=Test Groups,DC=Halo,DC=Net" Creates the groups contained in the 150410093716_HALO_Group_Dump.xml backup file in the contoso.com domain. Creates groups in the 'Test Groups' OU. Does not create Groups if they already exist. Does not create Groups if the target OU does not exist. Writes a log file of all function actions. .OUTPUTS Date and time stamped log file, e.g. 150410110533_AD_Group_Mirror.log, for use with Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ EXIT CODES: 1 - Report file not found 2 - Custom XML Group file not found .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory=$True,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #The source backup file [parameter(Mandatory=$True,Position=2)] [ValidateScript({Test-Path -Path $_})] [String]$BackupXml, #Optional target OU [parameter(Position=3)] [ValidateScript({Get-ADOrganizationalUnit -Identity $_})] [String]$TargetOu ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ############################## ## FUNCTION - Log-ScriptEvent ############################## <# Write a line of data to a script log file in a format that can be parsed by Trace32.exe / CMTrace.exe The severity of the logged line can be set as: 1 - Information 2 - Warning 3 - Error Warnings will be highlighted in yellow. Errors are highlighted in red. The tools: SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ #> Function Log-ScriptEvent { #Define and validate parameters [CmdletBinding()] Param( #Path to the log file [parameter(Mandatory=$True)] [String]$NewLog, #The information to log [parameter(Mandatory=$True)] [String]$Value, #The source of the error [parameter(Mandatory=$True)] [String]$Component, #The severity (1 - Information, 2- Warning, 3 - Error) [parameter(Mandatory=$True)] [ValidateRange(1,3)] [Single]$Severity ) #Obtain UTC offset $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime $DateTime.SetVarDate($(Get-Date)) $UtcValue = $DateTime.Value $UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21) #Create the line to be logged $LogLine = "<![LOG[$Value]LOG]!>" +` "<time=`"$(Get-Date -Format HH:mm:ss.fff)$($UtcOffset)`" " +` "date=`"$(Get-Date -Format M-d-yyyy)`" " +` "component=`"$Component`" " +` "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +` "type=`"$Severity`" " +` "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +` "file=`"`">" #Write the line to the passed log file Add-Content -Path $NewLog -Value $LogLine } #End of Function Log-ScriptEvent ########################################################################################################## ######## ## Main ######## #Create a variable to represent a new script log, constructing the report name from date details $NewReport = ".\$(Get-Date -Format yyMMddHHmmss)_AD_Group_Mirror.log" #Make sure the script log has been created if (New-Item -ItemType File -Path $NewReport) { ##Start writing to the script log (Start_Script) Log-ScriptEvent $NewReport ("=" * 90) "Start-Script" 1 Log-ScriptEvent $NewReport "TARGET_DOMAIN: $Domain" "Start_Script" 1 Log-ScriptEvent $NewReport "BACKUP_SOURCE: $BackupXml" "Start_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Start_Script" 1 Log-ScriptEvent $NewReport " " " " 1 #Instantiate an object for the target domain $TargetDomain = Get-ADDomain -Identity $Domain #Obtain the target domain FQDN $TargetDomainFqdn = $TargetDomain.DNSRoot #Obtain the target domain DN $TargetDomainDn = $TargetDomain.DistinguishedName #Import the OU information contained in the XML file $GroupInfo = Import-Clixml -Path $BackupXml -ErrorAction SilentlyContinue #Make sure we have custom group info if ($GroupInfo) { #Log custom XML import success Log-ScriptEvent $NewReport "Custom Group objects successfully imported from $BackupXml" "Mirror_Groups" 1 Log-ScriptEvent $NewReport " " " " 1 #Obtain the source domain DN from the first custom group object $SourceDomainDn = ($GroupInfo| Select -First 1).DomainDn #Create counters $i = 0 # groups processed $j = 0 # groups matched $k = 0 # groups created $l = 0 # group members processed $m = 0 # group creation failed $n = 0 # group members failed $o = 0 # groups processed (2) #Loop through each of the custom group objects foreach ($Group in $GroupInfo) { #Test that the group SamAccountName doesn't already exist try {$TargetGroupSAM = Get-ADGroup -Identity $Group.SamAccountName -Server $TargetDomainFqdn} catch {} #If we have a group then onwards... if ($TargetGroupSAM) { #Log that object exists Log-ScriptEvent $NewReport "SamAccountName - `"$(($Group).SamAccountName)`" - already exists in $Domain" "Mirror_Groups" 1 #Increment group matched counter $j++ } #End of if ($TargetGroupSAM) else { #Log that object does not exist Log-ScriptEvent $NewReport "SamAccountName - `"$(($Group).SamAccountName)`" - does not exist in $Domain" "Mirror_Groups" 1 #Test that the group Name doesn't already exist try{$TargetgroupName = Get-ADGroup -Identity $Group.Name -Server $TargetDomainFqdn} catch {} #If we have a group then onwards... if ($TargetgroupName) { #Log that object exists Log-ScriptEvent $NewReport "Group Name - `"$(($Group).Name)`" - already exists in $Domain" "Mirror_Groups" 1 #Increment group matched counter $j++ } #End of if ($TargetgroupName) else { #Log that object does not exist Log-ScriptEvent $NewReport "Group Name - `"$(($Group).Name)`" - does not exist in $Domain" "Mirror_Groups" 1 #Update the managedBy attribute if it exists if ($Group.managedBy) { #Replace domain portion of DN $ManagedBy = $Group.managedBy -replace $SourceDomainDn,$TargetDomainDn } #end of if ($Group.managedBy) #Determine where we create the group if ($TargetOu) { #Log that we are using a parameter value as our target OU Log-ScriptEvent $NewReport "Using supplied paramter - $TargetOu - as group parent OU" "Mirror_Groups" 1 #Attempt to create group in Target OU $Newgroup = New-ADgroup -Name $Group.Name ` -GroupCategory $Group.GroupCategory ` -GroupScope $Group.GroupScope ` -SamAccountName $Group.SamAccountName ` -DisplayName $Group.DisplayName ` -Description $Group.Description ` -Path $TargetOu ` -ErrorAction SilentlyContinue #Check the success of the New-ADgroup cmdlet if ($?) { #Log success of New-ADgroup cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($Group).SamAccountName)`" succeeded." "Mirror_Groups" 1 #Increment group created counter $k++ } #End of if ($?) else { #Log failure of New-ADgroup cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($Group).SamAccountName)`" failed. $($Error[0].exception.message)" "Mirror_Groups" 3 #Increment group creation failed counter $m++ } #End of else ($?) } #End of if ($TargetOu) else { #Replace the domain DN with the target filter DN for our parent path $TargetParentDn = $Group.ParentDn �Replace $SourceDomainDn,$TargetDomainDn #Test that the parent exists Try{$TargetParent = Get-ADObject -Identity $TargetParentDn -Server $TargetDomainFqdn} Catch {} #Check to see that the parent OU already exists if ($TargetParent) { #Log that object exists Log-ScriptEvent $NewReport "`"$TargetParentDn`" parent already exists in $Domain" "Mirror_Groups" 1 #Attempt to create group in Target OU $Newgroup = New-ADgroup -Name $Group.Name ` -GroupCategory $Group.GroupCategory ` -GroupScope $Group.GroupScope ` -SamAccountName $Group.SamAccountName ` -DisplayName $Group.DisplayName ` -Description $Group.Description ` -Path $TargetParentDn ` -ErrorAction SilentlyContinue #Check the success of the New-ADgroup cmdlet if ($?) { #Log success of New-ADgroup cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($Group).SamAccountName)`" succeeded." "Mirror_Groups" 1 #Increment group created counter $k++ } #End of if ($?) else { #Log failure of New-ADgroup cmdlet Log-ScriptEvent $NewReport "Creation of `"$(($Group).SamAccountName)`" failed. $($Error[0].exception.message)" "Mirror_Groups" 3 #Increment group creation failed counter $m++ } #End of else ($?) } #End of if ($TargetParent) else { #Log that object does not exist Log-ScriptEvent $NewReport "`"$TargetParentDn`" parent does not exist in $Domain... group creation will not be attempted" "Mirror_Groups" 1 Log-ScriptEvent $NewReport " " " " 1 } #End of else ($TargetParent) } #End of else ($TargetOu) } #End of else ($TargetgroupName) } #End of else ($TargetGroupSAM) #Spin up a progress bar for each filter processed Write-Progress -Activity "Mirroring Groups to $TargetDomainFqdn" -Status "Processed: $i" -PercentComplete -1 #Increment the group processed counter $i++ #Nullify key variables $TargetGroupSAM = $null $TargetGroupName = $null $TargetGroupDn = $null $TargetParent = $null } #End of foreach($Group in $GroupInfo) #Now we need to loop through the groups again to process membership foreach ($Group in $GroupInfo) { #Replace the existing domain DN with the DN for group in the target domain $TargetGroupDn = $Group.GroupDn �Replace $SourceDomainDn,$TargetDomainDn #Spacer Log-ScriptEvent $NewReport " " " " 1 #Loop through the members attribute foreach ($Member in $Group.members) { #Replace the existing member DN with the DN for the member in the target domain $TargetMemberDn = $Member �Replace $SourceDomainDn,$TargetDomainDn #Attempt to add the member to the group $NewMember = Add-ADGroupMember -Identity $TargetGroupDn -Members $TargetMemberDn -Server $Domain -ErrorAction SilentlyContinue #Check the success of the New-ADGroupMember cmdlet if ($?) { #Log success of New-ADGroupMember cmdlet Log-ScriptEvent $NewReport "Addition of $TargetMemberDn to $TargetGroupDn succeeded." "Add_Members" 1 #Increment group addition counter $l++ } #End of if ($?) else { #Log failure of New-ADGroup cmdlet Log-ScriptEvent $NewReport "Addition of $TargetMemberDn to $TargetGroupDn failed. $($Error[0].exception.message)" "Add-Members" 3 #Increment group addition failed counter $n++ } #End of else ($?) #Nullify variable $TargetMemberDn = $null } #End of foreach ($Member in $Group.members) #Spin up a progress bar for each filter processed Write-Progress -Activity "Updating group membership in $TargetDomainFqdn" -Status "Groups processed: $o" -PercentComplete -1 #Increment the group processed counter $o++ #Nullify variable $TargetGroupDn = $null } #End of foreach($Group in $GroupInfo) } #End of if ($GroupInfo) else { #Log failure to import custom group XML object Log-ScriptEvent $NewReport "$BackupXml import failed" "Mirror_Groups" 3 Log-ScriptEvent $NewReport "Script execution stopped" "Mirror_Groups" 1 Log-ScriptEvent $NewReport ("=" * 90) "Mirror_Groups" 1 Write-Error "$BackupXml not found. Script execution stopped." Exit 2 } #End of else ($GroupInfo) #Close of the script log Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 Log-ScriptEvent $NewReport "GROUPS_PROCESSED: $i" "Finish_Script" 1 Log-ScriptEvent $NewReport "GROUPS_MATCHED: $j" "Finish_Script" 1 Log-ScriptEvent $NewReport "GROUPS_CREATED_SUCCESS: $k" "Finish_Script" 1 Log-ScriptEvent $NewReport "GROUPS_CREATED_FAILURE: $m" "Finish_Script" 1 Log-ScriptEvent $NewReport "MEMBERS_ADDED_SUCCESS: $l" "Finish_Script" 1 Log-ScriptEvent $NewReport "MEMBERS_ADDED_FAILURE: $n" "Finish_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 } #End of if (New-Item -ItemType File -Path $NewReport) else { #Write a custom error Write-Error "$NewReport not found. Script execution stopped." Exit 1 } #End of else (New-Item -ItemType File -Path $NewReport) } #end of function Mirror-ADGroups ########################## #FUNCTION 7: Dump-ADGpos ########################## function Dump-ADGpos { ########################################################################################################## <# .SYNOPSIS Backs up GPOs from a specified domain and includes additional GPO information. .DESCRIPTION The function backs up GPOs in a target domain and captures additional GPO management information, such as Scope of Management, Block Inheritance, Link Enabled, Link Order, Link Enforced and WMI Filters. The backup can then be used by a partner function to mirror GPOs in a test domain. Details: * Creates a XML file containing PSCustomObjects used by partner import function * Creates a XML file WMI filter details used by partner import function * Creates a CSV file of additional information for readability * Creates a folder containing HTML reports of settings for each GPO * Additional backup information includes SOM (Scope of Management) Path, Block Inheritance, Link Enabled, Link Order', Link Enforced and WMI Filter data * Each CSV SOM entry is made up of "DistinguishedName:BlockInheritance:LinkEnabled:LinkOrder:LinkEnforced" * Option to create a Migration Table (to then be manually updated) Requirements: * PowerShell GroupPolicy Module * PowerShell ActiveDirectory Module * Group Policy Management Console .EXAMPLE Dump-ADGpos -Domain wintiptoys.com -BackupFolder "\\wingdc01\backups\" This will backup all GPOs in the domain wingtiptoys.com and store them in a date and time stamped folder under \\wingdc01\backups\. .EXAMPLE Dump-ADGpos -Domain contoso.com -BackupFolder "c:\backups" -MigTable This will backup all GPOs in the domain contoso.com and store them in a date and time stamped folder under c:\backups\. A migration table, MigrationTable.migtable, will also be created for manual editing. .EXAMPLE Dump-ADGpos -Domain contoso.com -BackupFolder "c:\backups" -ModifiedDays 15 This will backup all GPOs in the domain contoso.com that have been modified within the last 15 days. The function will store the backed up GPOs in a date and time stamped folder under c:\backups\ .EXAMPLE Dump-ADGpos -Domain adatum.com -BackupFolder "c:\backups" -GpoGuid "b1e0e5ea-0d6b-48f1-a56c-0a98d8acd17b" This will backup the GPO identified by the following GUID - "b1e0e5ea-0d6b-48f1-a56c-0a98d8acd17b" - from the domain adatum.com The backed up GPO will be stored in a date and time stamped folder under c:\backups\ .OUTPUTS * Backup folder name in the format Year_Month_Day_HourMinuteSecond * Per-GPO HTML settings report in the format <backup-guid>__<gpo-guid>__<gpo-name>.html * GpoDetails.xml * Wmifilters.xml * GpoInformation.csv * MigrationTable.migtable (optional) EXIT CODES: 1 - GPMC not found .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory,GroupPolicy #Version: 2.4 <# - 2.1 - 19/08/2014 * the script now processes gPLink info on site objects * thanks to Mark Renoden [MSFT] - 2.2 - 08/07/2015 * updates to allow backup from one trusted forest to another - 2.3 - 12/01/2016 * added ability to backup GPOs modified within the last X days * added ability to create html report of settings per GPO * thanks to Marcus Carvalho [MSFT] - 2.4 - 15/01/2016 * added ability to backup a single GPO * added parameter sets to prevent -GpoGuid and -ModifiedDate being used together #> #Define and validate parameters [CmdletBinding(DefaultParameterSetName="All")] Param( #The target domain [parameter(Mandatory=$True,Position=1)] [ValidateScript({Get-ADDomain $_})] [String]$Domain, #The backup folder [parameter(Mandatory=$True,Position=2)] [ValidateScript({Test-Path $_})] [String]$BackupFolder, #Backup GPOs modified within the last X days [parameter(ParameterSetName="Modified",Mandatory=$False,Position=3)] [ValidateSet(15,30,45,60,90)] [Int]$ModifiedDays, #Backup a single GPO [parameter(ParameterSetName="Guid",Mandatory=$False,Position=3)] [ValidateScript({Get-GPO -Guid $_})] [String]$GpoGuid, #Whether to create a migration table [Switch]$MigTable ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ######## ## Main ######## ######################## ##BACKUP FOLDER DETAILS #Create a variable to represent a new backup folder #(constructing the report name from date details and the supplied backup folder) $Date = Get-Date $ShortDate = Get-Date -format d $SubBackupFolder = "$BackupFolder\" + ` "$($Date.Year)_" + ` "$("{0:D2}" -f $Date.Month)_" + ` "$("{0:D2}" -f $Date.Day)_" + ` "$("{0:D2}" -f $Date.Hour)" + ` "$("{0:D2}" -f $Date.Minute)" + ` "$("{0:D2}" -f $Date.Second)" ################## ##BACKUP ALL GPOs #Create the backup folder New-Item -ItemType Directory -Path $SubBackupFolder | Out-Null #Create the settings report folder $HtmlReports = "HTML_Reports" New-Item -ItemType Directory -Path "$SubBackupFolder\$HtmlReports" | Out-Null #Make sure the backup folders have been created if ((Test-Path -Path $SubBackupFolder) -and (Test-Path -Path "$SubBackupFolder\$HtmlReports")) { #Connect to the supplied domain $TargetDomain = Get-ADDomain -Identity $Domain #Obtain the domain FQDN $DomainFQDN = $TargetDomain.DNSRoot #Obtain the domain DN $DomainDN = $TargetDomain.DistinguishedName #Connect to the forest root domain $TargetForestRootDomain = (Get-ADForest -Server $DomainFQDN).RootDomain | Get-ADDomain #Obtain the forest FQDN $ForestFQDN = $TargetForestRootDomain.DNSRoot #Obtain the forest DN $ForestDN = $TargetForestRootDomain.DistinguishedName #Create an empty array for our backups $Backups = @() #Determine the type of backup to be performed if ($ModifiedDays) { #Get a list of $ModGpos = Get-GPO -Domain $DomainFQDN -All | Where-Object {$_.ModificationTime -gt $Date.AddDays(-$ModifiedDays)} #Loop through each recently changed GPO and back it up, adding the resultant object to the $Backups array foreach ($ModGpo in $ModGpos) { $Backups += Backup-GPO $ModGpo.DisplayName -Path $SubBackupFolder -Comment "Scripted backup created by $env:userdomain\$env:username on $ShortDate" } #end of foreach ($ModGpo in $ModGpos) } #end of if ($ModifiedDays) elseif ($GpoGuid) { #Backup single GPO $Backups = Backup-GPO -Guid $GpoGuid -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain\$env:username on $ShortDate" } #end of elseif ($GpoGuid) else { #Backup all GPOs found in the domain $Backups = Backup-GPO -All -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain\$env:username on $ShortDate" } #end of else ($ModifiedDays) #Instantiate an object for Group Policy Management (GPMC required) try { $GPM = New-Object -ComObject GPMgmt.GPM } #end of Try... catch { #Display exit message to console $Message = "ERROR: Unable to connect to GPMC. Please check that it is installed." Write-Host Write-Error $Message #Exit the script exit 1 } #end of Catch... #Import the GPM API constants $Constants = $GPM.getConstants() #Connect to the supplied domain $GpmDomain = $GPM.GetDomain($DomainFQDN,$Null,$Constants.UseAnyDc) #Connect to the sites container $GpmSites = $GPM.GetSitesContainer($ForestFQDN,$DomainFQDN,$Null,$Constants.UseAnyDc) ################################### ##COLLECT SPECIFIC GPO INFORMATION #Loop through each backed-up GPO foreach ($Backup in $Backups) { #Get the GPO GUID for our target GPO $GpoGuid = $Backup.GpoId #Get the backup GUID for our target GPO $BackupGuid = $Backup.Id #Instantiate an object for the relevant GPO using GPM $GPO = $GpmDomain.GetGPO("{$GpoGuid}") #Get the GPO DisplayName property $GpoName = $GPO.DisplayName #Get the GPO ID property $GpoID = $GPO.ID ##Retrieve SOM Information #Create a GPM search criteria object $GpmSearchCriteria = $GPM.CreateSearchCriteria() #Configure search critera for SOM links against a GPO $GpmSearchCriteria.Add($Constants.SearchPropertySOMLinks,$Constants.SearchOpContains,$GPO) #Perform the search $SOMs = $GpmDomain.SearchSOMs($GpmSearchCriteria) + $GpmSites.SearchSites($GpmSearchCriteria) #Empty the SomPath variable $SomInfo = $Null #Loop through any SOMs returned and write them to a variable foreach ($SOM in $SOMs) { #Capture the SOM Distinguished Name $SomDN = $SOM.Path #Capture Block Inheritance state $SomInheritance = $SOM.GPOInheritanceBlocked #Get GPO Link information for the SOM $GpoLinks = $SOM.GetGPOLinks() #Loop through the GPO Link information and match info that relates to our current GPO foreach ($GpoLink in $GpoLinks) { if ($GpoLink.GPOID -eq $GpoID) { #Capture the GPO link status $LinkEnabled = $GpoLink.Enabled #Capture the GPO precedence order $LinkOrder = $GpoLink.SOMLinkOrder #Capture Enforced state $LinkEnforced = $GpoLink.Enforced } #end of if ($GpoLink.GPOID -eq $GpoID) } #end of foreach ($GpoLink in $GpoLinks) #Append the SOM DN, link status, link order and Block Inheritance info to $SomInfo [Array]$SomInfo += "$SomDN`:$SomInheritance`:$LinkEnabled`:$LinkOrder`:$LinkEnforced" } #end of foreach ($SOM in $SOMs)... ##Obtain WMI Filter path using Get-GPO $Wmifilter = (Get-GPO -Guid $GpoGuid -Domain $DomainFQDN).WMifilter.Path #Split the value down and use the ID portion of the array #$WMifilter = ($Wmifilter -split "`"")[1] $WMifilter = ($Wmifilter -split '"')[1] #Add selected GPO properties to a custom GPO object $GpoInfo = [PSCustomObject]@{ BackupGuid = $BackupGuid Name = $GpoName GpoGuid = $GpoGuid SOMs = $SomInfo DomainDN = $DomainDN Wmifilter = $Wmifilter } #end of $Properties... #Add our new object to an array [Array]$TotalGPOs += $GpoInfo } #end of foreach ($Backup in $Backups)... ##################### ##BACKUP WMI FILTERS #Connect to the Active Directory to get details of the WMI filters $Wmifilters = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"' ` -Properties msWMI-Author, msWMI-ID, msWMI-Name, msWMI-Parm1, msWMI-Parm2 ` -Server $DomainFQDN ` -ErrorAction SilentlyContinue ###################### ##CREATE REPORT FILES ##XML reports #Create a variable for the XML file representing custom information about the backed up GPOs $CustomGpoXML = "$SubBackupFolder\GpoDetails.xml" #Export our array of custom GPO objects to XML so they can be easily re-imported as objects $TotalGPOs | Export-Clixml -Path $CustomGpoXML #if $WMifilters contains objects write these to an XML file if ($Wmifilters) { #Create a variable for the XML file representing the WMI filters $WmiXML = "$SubBackupFolder\Wmifilters.xml" #Export our array of WMI filters to XML so they can be easily re-imported as objects $Wmifilters | Export-Clixml -Path $WmiXML } #end of if ($Wmifilters) ##CSV report / HTML Settings reports #Create a variable for the CSV file that will contain the SOM (Scope of Management) information for each backed-up GPO $SOMReportCSV = "$SubBackupFolder\GpoInformation.csv" #Now, let's create the CSV report and the HTML settings reports foreach ($CustomGPO in $TotalGPOs) { ##CSV report stuff #Start constructing the CSV file line entry for the current GPO $CSVLine = "`"$($CustomGPO.Name)`",`"{$($CustomGPO.GPOGuid)}`"," #Expand the SOMs property of the current object $CustomSOMs = $CustomGPO.SOMs #Loop through any SOMs returned foreach ($CustomSOM in $CustomSOMs) { #Append the SOM path to our CSV line $CSVLine += "`"$CustomSOM`"," } #end of foreach ($CustomSOM in $CustomSOMs)... #Write the newly constructed CSV line to the report Add-Content -Path $SOMReportCSV -Value $CSVLine ##HTML settings report stuff #Remove invalid characters from GPO display name $GpoCleanedName = $CustomGPO.Name -replace "[^1-9a-zA-Z_]", "_" #Create path to html file $ReportPath = "$SubBackupFolder\$HtmlReports\$($CustomGPO.BackupGuid)___$($CustomGPO.GpoGuid)__$($GpoCleanedName).html" #Create GPO report Get-GPOReport -Guid $CustomGPO.GpoGuid -Path $ReportPath -ReportType HTML } #end of foreach ($CustomGPO in $TotalGPOs)... ########### ##MIGTABLE #Check whether a migration table should be created if ($MigTable) { #Create a variable for the migration table $MigrationFile = "$SubBackupFolder\MigrationTable.migtable" #Create a migration table $MigrationTable = $GPM.CreateMigrationTable() #Connect to the backup directory $GpmBackupDir = $GPM.GetBackUpDir($SubBackupFolder) #Reset the GPM search criterea $GpmSearchCriteria = $GPM.CreateSearchCriteria() #Configure search critera for the most recent backup $GpmSearchCriteria.Add($Constants.SearchPropertyBackupMostRecent,$Constants.SearchOpEquals,$True) #Get GPO information $BackedUpGPOs = $GpmBackupDir.SearchBackups($GpmSearchCriteria) #Add the information to our migration table foreach ($BackedUpGPO in $BackedUpGPOs) { $MigrationTable.Add($Constants.ProcessSecurity,$BackedUpGPO) } #end of foreach ($BackedUpGPO in $BackedUpGPOs)... #Save the migration table $MigrationTable.Save($MigrationFile) } #end of if ($MigTable)... } #end of if ((Test-Path -Path $SubBackupFolder) -and (Test-Path -Path "$SubBackupFolder\$HtmlReports"))... else { #Write error Write-Error -Message "Backup path validation failed" } #end of ((Test-Path -Path $SubBackupFolder) -and (Test-Path -Path "$SubBackupFolder\$HtmlReports")) } #end of function Dump-ADGpos ############################ #FUNCTION 8: Mirror-ADGpos ############################ function Mirror-ADGpos { ########################################################################################################## <# .SYNOPSIS Imports all GPOs from a backup folder into a test domain. Additional GPO information can be imported. .DESCRIPTION The function is intended to import backed up GPOs to a test domain. For the additional GPO information functionality, a backup created by the partner Dump-ADGpos function should be used. Details: * Can use a Migration Table to translate domain specific information * Can import SOM (Scope of Management) Path, Block Inheritance, Link Enabled, Link Order and Enforced settings * Can import and link WMI filters * If set by the function, 'Block Inheritance' and 'Enforced' settings are highlighted as warnings (yellow) in the function log Requirements: * PowerShell GroupPolicy Module * PowerShell ActiveDirectory Module * A backup created by partner Dump-ADGpos function * Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) to view function log * SOM paths, e.g. OU heirachy, in target domain matches source domain to reinstate additional information .EXAMPLE Mirror-ADGpos -Domain northwindtraders.com -BackupFolder "\\corpdc01\backups\" This will import all backed-up GPOs from \\corpdc01\backups into the northwindtraders domain. No additional GPO infomation is imported. .EXAMPLE Mirror-ADGpos -Domain fabrikam.com -BackupFolder "d:\backups" -MigTable This will import all backed-up GPOs from d:\backups into the fabrikam domain. The import will look for a migration table in the backup folder and attempt to translate the values. .EXAMPLE Mirror-ADGpos -Domain northwindtraders.com -BackupFolder "\\corpdc01\backups\" -SomInfo This will import all backed-up GPOs from \\corpdc01\backups into the northwindtraders domain. The import will attempt to recreate GPO links and their precedence. Block Inheritance and Enforced details will also be restored, if possible. .EXAMPLE Mirror-ADGpos -Domain northwindtraders.com -BackupFolder "\\corpdc02\backups\" -WmiFilter This will import all backed-up GPOs from \\corpdc02\backups into the northwindtraders domain. The import will attempt to recreate WMI filters and link them to matching policies. .EXAMPLE Mirror-ADGpos -Domain fabrikam.com -BackupFolder "d:\backups" -MigTable -SomInfo -WMiFilter This will import all backed-up GPOs from d:\backups into the fabrikam domain. The import will look for a migration table in the backup folder and attempt to translate the values. The import will also attempt to recreate GPO links and their precedence. Block Inheritance and Enforced details will also be restored, if possible. The import will attempt to recreate WMI filters and link them to matching policies. .OUTPUTS Time and date stamped import log for use with Trace32.exe (SMS Trace) or CMTrace.exe (Configuration Manager Trace Log Tool) SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ EXIT CODES: 1 - Report file not found 2 - Custom GPO XML file not found 3 - Migration file not found .NOTES THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. This sample is not supported under any Microsoft standard support program or service. The script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the script be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample or documentation, even if Microsoft has been advised of the possibility of such damages, rising out of the use of or inability to use the sample script, even if Microsoft has been advised of the possibility of such damages. #> ########################################################################################################## ################################# ## Function Options and Parameters ################################# #Requires -version 3 #Requires -modules ActiveDirectory,GroupPolicy #Version: 2.0 #Define and validate parameters [CmdletBinding()] Param( #The target domain [parameter(Mandatory=$True,Position=1)] [ValidateScript({Get-ADDomain -Identity $_})] [String]$Domain, #The source backup folder (use full path) [parameter(Mandatory=$True,Position=2)] [ValidateScript({Test-Path -Path $_})] [String]$BackupFolder, # Whether to reference a migration table [Switch] $MigTable, # Whether to import SOM information [Switch] $SomInfo, # Whether to import WMI filter information [Switch] $WmiFilter ) #Set strict mode to identify typographical errors (uncomment whilst editing script) #Set-StrictMode -version Latest ########################################################################################################## ############################## ## FUNCTION - Log-ScriptEvent ############################## <# Write a line of data to a script log file in a format that can be parsed by Trace32.exe / CMTrace.exe The severity of the logged line can be set as: 1 - Information 2 - Warning 3 - Error Warnings will be highlighted in yellow. Errors are highlighted in red. The tools: SMS Trace - http://www.microsoft.com/en-us/download/details.aspx?id=18153 CM Trace - Installation directory on Configuration Manager 2012 Site Server - <Install Directory>\tools\ #> Function Log-ScriptEvent { #Define and validate parameters [CmdletBinding()] Param( #Path to the log file [parameter(Mandatory=$True)] [String]$NewReport, #The information to log [parameter(Mandatory=$True)] [String]$Value, #The source of the error [parameter(Mandatory=$True)] [String]$Component, #The severity (1 - Information, 2- Warning, 3 - Error) [parameter(Mandatory=$True)] [ValidateRange(1,3)] [Single]$Severity ) #Create the line to be logged $LogLine = "<![LOG[$Value]LOG]!>" +` "<time=`"$(Get-Date -Format HH:mm:ss).000+0`" " +` "date=`"$(Get-Date -Format M-d-yyyy)`" " +` "component=`"$Component`" " +` "context=`"`" " +` "type=`"$Severity`" " +` "thread=`"1`" " +` "file=`"`">" #Write the line to the passed log file Add-Content -Path $NewReport -Value $LogLine } ########################################################################################################## ######## ## Main ######## #Create a variable to represent a new script log, constructing the report name from date details $SourceParent = (Get-Location).Path $Date = Get-Date #-Format yyMMddhhss $NewReport = "$SourceParent\" + ` "$($Date.Year)" + ` "$("{0:D2}" -f $Date.Month)" + ` "$("{0:D2}" -f $Date.Day)" + ` "$("{0:D2}" -f $Date.Hour)" + ` "$("{0:D2}" -f $Date.Minute)" + ` "$("{0:D2}" -f $Date.Second)" + ` "_GPO_Import.log" #Make sure the script log has been created If (New-Item -ItemType File -Path $NewReport) { ##Start writing to the script log (Start_Script) Log-ScriptEvent $NewReport ("=" * 90) "Start-Script" 1 Log-ScriptEvent $NewReport "TARGET_DOMAIN: $Domain" "Start_Script" 1 Log-ScriptEvent $NewReport "BACKUP_SOURCE: $BackupFolder" "Start_Script" 1 Log-ScriptEvent $NewReport "MIGRATION_TABLE: $MigTable" "Start_Script" 1 Log-ScriptEvent $NewReport "SOM_INFO: $SomInfo" "Start_Script" 1 Log-ScriptEvent $NewReport "WMI_FILTERS: $WmiFilter" "Start_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Start_Script" 1 Log-ScriptEvent $NewReport " " " " 1 ##Define variables used throughout the script sections #Instantiate an object for the target domain $TargetDomain = Get-ADDomain $Domain #Obtain the target domain FQDN $TargetDomainFQDN = $TargetDomain.DNSRoot #Obtain the target domain DN $TargetDomainDN = $TargetDomain.DistinguishedName #Obtain the target domain PDCe $TargetPDC = $TargetDomain.PDCEmulator #Create a variable for the Custom GPO XML file $CustomGpoXML = "$BackupFolder\GpoDetails.xml" #Import the custom GPO information contained in the XML file $CustomGpoInfo = Import-Clixml -Path $CustomGpoXML #Obtain the source domain DN from the first custom GPO object $SourceDomainDN = ($CustomGpoInfo | Select -First 1).DomainDN ################################## ###Section 1 - Import WMI filters ##Create or update WMI filters in Active Directory if the -WmiFilter switch is specified (Import_WMI) #Make sure we have custom GPO info If ($CustomGpoInfo) { #Log custom GPO import success Log-ScriptEvent $NewReport "Custom GPO objects successfully imported from $CustomGpoXML" "Import_GPOs" 1 Log-ScriptEvent $NewReport " " " " 1 #Check whether the WMI filters should be imported If ($WmiFilter) { #Create a variable for the XML file representing the WMI filters $WmiXML = "$BackupFolder\WmiFilters.xml" #Import the WMI filter information contained in the XML file $WmiFilters = Import-Clixml -Path $WmiXML #Make sure we have WMI filter information If ($WmiFilters) { #Log WMI filter XML import success Log-ScriptEvent $NewReport "WMI f$Tailter objects successfully imported from $WmiXML" "Import_WMI" 1 #Create a filter counter $k = 0 #Loop through each of the WMI filters ForEach ($WMI in $WmiFilters) { #Replace the domain DN with the target filter DN $TargetWmiDN = $WMI.DistinguishedName �Replace $SourceDomainDN, $TargetDomainDN #Ensure that the msWMI-Parm1 property (the WMI Filter Description in the GUI) is populated If (!($WMI."msWMI-Parm1")) { #Set the description as a single space to avoid an error $Parm1 = " " } #End of If (!($WMI."msWMI-Parm1")) Else { #Use the current filter's description property $Parm1 = $WMI."msWMI-Parm1" } #End of Else (!($WMI."msWMI-Parm1")) #Test that the WMI filter doesn't already exist $TargetWMI = (Get-ADObject -Identity $TargetWmiDN -Server $TargetPDC -ErrorAction SilentlyContinue) #If the object already exists then just update it If ($TargetWMI) { #Log that object exists Log-ScriptEvent $NewReport "`"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" already exists in $Domain - attempting to update..." "Import_WMI" 1 #Define properties to be passed to Set-ADObject $Properties = [Ordered]@{ "msWMI-Author" = $WMI."msWMI-Author" "msWMI-ChangeDate" = "$(Get-Date -Format yyyyMMddhhmmss).706000-000" "msWMI-ID" = $WMI."msWMI-ID" "msWMI-Name" = $WMI."msWMI-Name" "msWMI-Parm1" = $Parm1 "msWMI-Parm2" = $WMI."msWMI-Parm2" } #End of $Properties #Update the AD object $UpdateWmiFilter = Set-ADObject -Identity $TargetWmiDN -Replace $Properties -Server $TargetPDC -ErrorAction SilentlyContinue #Check the success of the Set-ADObject cmdlet If ($?) { #Log success of Set-ADObject cmdlet Log-ScriptEvent $NewReport "Update of `"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" succeeded." "Import_WMI" 1 } #End of If ($?) Else { #Log failure of Set-ADObject cmdlet Log-ScriptEvent $NewReport "Update of `"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" failed. $($Error[0].exception.message)" "Import_WMI" 3 } #End of Else ($?) } #End of If ($TargetWMI) Else { #Log that object does not exist Log-ScriptEvent $NewReport "`"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" does not exist in $Domain - attempting to create..." "Import_WMI" 1 #Define properties to be passed to Set-ADObject $Properties = [Ordered]@{ "msWMI-Author" = $WMI."msWMI-Author" "msWMI-ChangeDate" = "$(Get-Date -Format yyyyMMddhhmmss).706000-000" "msWMI-CreationDate" = "$(Get-Date -Format yyyyMMddhhmmss).706000-000" "msWMI-ID" = $WMI."msWMI-ID" "msWMI-Name" = $WMI."msWMI-Name" "msWMI-Parm1" = $Parm1 "msWMI-Parm2" = $WMI."msWMI-Parm2" } #End of $Properties #Create the AD object $NewWmiFilter = New-ADObject -Name $WMI."msWMI-ID" -Type $WMI.ObjectClass ` -Path "CN=SOM,CN=WMIPolicy,CN=System,$TargetDomainDN" ` -OtherAttributes $Properties ` -Server $TargetPDC ` -ErrorAction SilentlyContinue #Check the success of the New-ADObject cmdlet If ($?) { #Log success of New-ADObject cmdlet Log-ScriptEvent $NewReport "Creation of `"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" succeeded." "Import_WMI" 1 } #End of If ($?) Else { #Log failure of New-ADObject cmdlet Log-ScriptEvent $NewReport "`"$($WMI."msWMI-Name") - $($WMI."msWMI-ID")`" failed. $($Error[0].exception.message)" "Import_WMI" 3 } #End of Else (?) } #End of Else ($TargetWMI) #Spin up a progress bar for each filter processed Write-Progress -activity "Importing WMI filters to $TargetDomainFQDN" -status "Processed: $k" -percentcomplete -1 #Increment the filter counter $k++ } #End of ForEach ($WMI in $WmiFilters) } #End of If ($WmiFilters) Else { #Log WMI filter XML import failure Log-ScriptEvent $NewReport "WMI filter objects import failed from $WmiXML" "Import_WMI" 3 } #End of Else ($WmiFilters) } #End of If ($WmiFilter) ##################################### ###Section 2 - Import backed up GPOs ##Perform a standard Import-GPO with or without a Migration Table (Import_GPOs) #A counter for each GPO processed $i = 0 #Loop through each Custom GPO object from the custom GPO array ForEach ($CustomGpo in $CustomGpoInfo) { #Log current GPO name Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport "Processing policy - $($CustomGpo.Name)..." "Import_GPOs" 1 #Check whether we're using a migration table for the GPO import If ($MigTable) { #Create a variable for the migration table $MigrationFile = "$BackupFolder\MigrationTable.migtable" #Check that a migration table has been created by the backup function If (Test-Path -Path $MigrationFile) { #Log migration check Log-ScriptEvent $NewReport "The import is referencing $MigrationFile" "Import_GPOs" 1 #Import all the GPOs referenced in the backup folder with a migration table $ImportedGpo = Import-GPO -BackupId $CustomGpo.BackupGuid ` -Path $BackupFolder ` -CreateIfNeeded ` -Domain $TargetDomainFQDN ` -TargetName $CustomGpo.Name ` -MigrationTable $MigrationFile ` -Server $TargetPDC ` -ErrorAction SilentlyContinue #Log the outcome of $ImportedGpo If ($?) { Log-ScriptEvent $NewReport "Import of $($CustomGpo.Name) successful" "Import_GPOs" 1 Log-ScriptEvent $NewReport "$($CustomGpo.Name) has guid - $($ImportedGpo.Id)" "Import_GPOs" 1 } #End of If ($?)... Else { Log-ScriptEvent $NewReport "Import of $($CustomGpo.Name) failed. $($Error[0].exception.message)" "Import_GPOs" 3 } #End of Else ($?)... } #End of If (Test-Path -Path $MigrationFile)... Else { #Record that the migration table isn't present and exit Log-ScriptEvent $NewReport "$MigrationFile not found. " "Import_GPOs" 3 Log-ScriptEvent $NewReport "Script execution stopped" "Import_GPOs" 1 Log-ScriptEvent $NewReport ("=" * 90) "Import_GPOs" 1 Write-Error "$MigrationFile not found. Script execution stopped." Exit 3 } #End of Else (Test-Path -Path $MigrationFile)... } #End of If ($MigTable)... Else { #Import all the GPOs referenced in the backup folder $ImportedGpo = Import-GPO -BackupId $CustomGpo.BackupGuid ` -Path $BackupFolder ` -CreateIfNeeded ` -Domain $TargetDomainFQDN ` -TargetName $CustomGpo.Name ` -Server $TargetPDC ` -ErrorAction SilentlyContinue #Log the outcome of $ImportedGpo If ($?) { Log-ScriptEvent $NewReport "Import of $($CustomGpo.Name) successful" "Import_GPOs" 1 Log-ScriptEvent $NewReport "$($CustomGpo.Name) has guid - $($ImportedGpo.Id)" "Import_GPOs" 1 } #End of If ($?)... Else { Log-ScriptEvent $NewReport "Import of $($CustomGpo.Name) failed. $($Error[0].exception.message)" "Import_GPOs" 3 } #End of Else ($?)... } #End of Else ($MigTable)... ################################ ###Section 3 - Link WMI filters ##Link previously updated WMI filters to GPOs (Update_WMI) #Check whether the a -WmiFilter switch was supplied at function execution If ($WmiFilter) { #Check whether the current GPO custom object has a WMI filter associated If ($CustomGpo.WmiFilter) { #Log filter found Log-ScriptEvent $NewReport "Found filter entry: $($CustomGpo.WmiFilter)" "Update_WMI" 1 ##Check that the associated filter exists in the target doamin #Contruct the target WMI DN $TargetWmiDN = "CN=$($CustomGpo.WmiFilter),CN=SOM,CN=WMIPolicy,CN=System,$TargetDomainDN" #Test that the WMI filter exists $TargetWMI = Get-ADObject -Identity $TargetWmiDN -Property "msWMI-Name" -Server $TargetPDC -ErrorAction SilentlyContinue #If the object already exists then link it to the current GPO If ($TargetWMI) { #Log that WMI object exists Log-ScriptEvent $NewReport "`"$($TargetWMI."msWMI-Name") - $($TargetWMI.Name)`" WMI filter already exists in $Domain" "Update_WMI" 1 ##We'll have to update an attribute on the GPO object in AD #Contruct the target GPO DN $TargetGpoDN = "CN={$($ImportedGpo.Id)},CN=Policies,CN=System,$TargetDomainDN" #Update the GPO attribute in AD $UpdateGpoFilter = Set-ADObject $TargetGpoDN -Replace @{gPCWQLFilter = "[$TargetDomainFQDN;$($TargetWMI.Name);0]"} -Server $TargetPDC -ErrorAction SilentlyContinue #Check the success of the Set-ADObject cmdlet If ($?) { #Log success of Set-ADObject cmdlet Log-ScriptEvent $NewReport "Link of `"$($TargetWMI."msWMI-Name") - $($TargetWMI.Name)`" to $TargetGpoDN succeeded." "Update_WMI" 1 } #End of If ($?) Else { #Log failure of Set-ADObject cmdlet Log-ScriptEvent $NewReport "Link of `"$($TargetWMI."msWMI-Name") - $($TargetWMI.Name)`" to $TargetGpoDN failed. $($Error[0].exception.message)" "Update_WMI" 3 } #End of Else (?) } #End of If ($TargetWMI) Else { #Log that WMI object does not exist Log-ScriptEvent $NewReport "`"$($TargetWMI."msWMI-Name") - $($TargetWMI.Name)`" WMI filter does not exist in $Domain" "Update_WMI" 3 } #End of Else ($TargetWMI) } #End of If ($CustomGpo.WmiFilter) } #End of If ($WmiFilter) ############################### ###Section 4 - Create GPO links ##Creating the necessary GPO links is a two part process.. part one ensures that the GPO links are present (Create_Links) #Check whether the -SomInfo switch was supplied at function execution If ($SomInfo) { #Check whether the GPO has any SOM information If ($CustomGpo.SOMs) { #Get a list of any associated SOMs $SOMs = $CustomGpo | Select-Object -ExpandProperty SOMs #Log SOMs found Log-ScriptEvent $NewReport "Found SOM entries: $SOMs" "Create_Links" 1 #Loop through each SOM and associate it with a target ForEach ($SOM in $SOMs) { #Get the DN part from the SOM entry $SomDN = ($SOM �Split ":")[0] #Replace the domain DNs $SomDN = $SomDN �Replace $SourceDomainDN, $TargetDomainDN #Log SOM DN update Log-ScriptEvent $NewReport "SOM DN set as $SomDN" "Create_Links" 1 #Check the SOM target exists $TargetSom = Get-ADObject -Identity $SomDn -Server $TargetPDC -ErrorAction SilentlyContinue If ($?) { #Log confirmation of SOM target Log-ScriptEvent $NewReport "$SomDn exists in target domain" "Create_Links" 1 #Create a corresponding SOM link $SomLink = New-GPLink -Guid $ImportedGpo.Id -Domain $TargetDomainFQDN -Target $SomDN -Server $TargetPDC -ErrorAction SilentlyContinue #Log the outcome of $SomLink If ($?) { Log-ScriptEvent $NewReport "GPO Link created for $($ImportedGPO.Id) at $SomDn" "Create_Links" 1 } #End of If ($?)... Else { Log-ScriptEvent $NewReport "Creation of GPO link at $SomDn failed. $($Error[0].exception.message)" "Create_Links" 3 } #End of Else ($?)... } #End of If ($?) ($TargetSom)... Else { #Log failure to verify SOM target Log-ScriptEvent $NewReport "$SomDn does not exist in target domain" "Create_Links" 3 } #End of Else ($?)... } #End of ForEach ($SOM in $SOMs)... } #End of If ($CustomGpo.SOMs)... #Add the GPO guid from the new domain to our custom GPO information $CustomGpo | Add-Member -MemberType NoteProperty -Name NewGpoGuid -Value $ImportedGpo.Id } #End of If ($SomInfo)... #Spin up a progress bar for each GPO processed Write-Progress -activity "Importing Group Policies to $TargetDomainFQDN" -status "Processed: $i" -percentcomplete -1 #Increment the GPO processed counter $i++ } #End of ForEach ($CustomGpo in $CustomGpoInfo)... ################################## ###Section 5 - Configure GPO Links ##This is part two of the SOM / GPO link creation process (Update_Links) #Check whether the -SomInfo switch was supplied at function execution If ($SomInfo) { #A counter for each GPO linked $j = 0 #We need to loop through $CustomGPOInfo again and set enabled status and precendence on GPO links ForEach ($CustomGpo in $CustomGpoInfo) { #Check whether the GPO has any SOM information If ($CustomGpo.SOMs) { #Log current GPO name Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport "Processing GPO link updates for $($CustomGpo.Name)..." "Update_Links" 1 #Get a list of any associated SOMs $SOMs = $CustomGpo | Select -ExpandProperty SOMs #Loop through each SOM and associate it with a target ForEach ($SOM in $SOMs) { #Get the DN part from the SOM entry $SomDN = ($SOM -Split ":")[0] #Replace the domain DNs $SomDN = $SomDN �Replace $SourceDomainDN, $TargetDomainDN #Check the SOM target exists $TargetSom = Get-ADObject -Identity $SomDn -Server $TargetPDC -ErrorAction SilentlyContinue If ($?) { #Determine the GPO link status of the SOM entry Switch ($SOM.Split(":")[2]) { $True { #Set the GPO link enabled variable to Yes $LinkEnabled = "Yes" } #End of $True $False { #Set the GPO link enabled variable to No $LinkEnabled = "No" } #End of $False } #End of Switch ($SOM.Split(":")[2]) #Get the GPO link order part of the SOM entry $LinkOrder = $SOM.Split(":")[3] #Determine the GPO enforced status of the SOM entry Switch ($SOM.Split(":")[4]) { $True { #Set the GPO link enabled variable to Yes $LinkEnforced = "Yes" } #End of $True $False { #Set the GPO link enabled variable to No $LinkEnforced = "No" } #End of $False } #End of Switch ($SOM.Split(":")[4]) #The SOM link has already been created, so now set the 'enabled', 'order' and 'enforced' properties $SomLink = Set-GPLink -Guid $CustomGpo.NewGpoGuid ` -Domain $TargetDomainFQDN ` -Target $SomDN ` -LinkEnabled $LinkEnabled ` -Order $LinkOrder ` -Enforced $LinkEnforced ` -Server $TargetPDC ` -ErrorAction SilentlyContinue #Log the outcome of $SomLink If ($?) { #Log $SomLink success details Log-ScriptEvent $NewReport "GPO link updated for $($ImportedGPO.Id) at $SomDn" "Update_Links" 1 #Log an Enforced entry as a warning (severity 2) If ($LinkEnforced -eq "Yes") { #Log with severity 2 Log-ScriptEvent $NewReport "GPO link set to `"Enabled: $LinkEnabled`" `"Order: $LinkOrder`" `"ENFORCED: $($LinkEnforced.ToUpper())`"" "Update_Links" 2 } #End of If ($LinkEnforced -eq "Yes") Else { #Log with severity 1 Log-ScriptEvent $NewReport "GPO link set to `"Enabled: $LinkEnabled`" `"Order: $LinkOrder`" `"Enforced: $LinkEnforced`"" "Update_Links" 1 } #End of Else ($LinkEnforced -eq "Yes") #Increment the GPO linked counter $j++ } #End of If ($?) ($SomLink)... Else { #Log $SomLink failure details Log-ScriptEvent $NewReport "Creation of GPO link at $SomDn failed. $($Error[0].exception.message)" "Update_Links" 3 } #End of Else ($?) ($SomLink)... #Get the block inheritance part of the SOM entry $SomInheritance = $SOM.Split(":")[1] #Check if we need should set Block Inheritance If ($SomInheritance -eq $True) { #Set block inheritance $SetInheritance = Set-GPInheritance -Target $SomDn -IsBlocked Yes -Server $TargetPDC -ErrorAction SilentlyContinue If ($?) { #Log failure to set block inheritance Log-ScriptEvent $NewReport "BLOCK INHERITANCE set on $SomDn" "Update_Links" 2 } #End of If ($?) ($SetInheritance)... Else { #Log failure to set block inheritance Log-ScriptEvent $NewReport "Can not set Block Inheritance on $SomDn" "Update_Links" 3 } #End of Else ($?) ($SetInheritance)... } #End of If ($SomInheritance -eq $True)... } # End of If ($?) ($TargetSom)... Else { #Log failure to verify SOM target Log-ScriptEvent $NewReport "$SomDn does not exist in target domain" "Update_Links" 3 } # End of Else ($?) ($TargetSom)... } #End of ForEach ($SOM in $SOMs)... } #End of If ($CustomGpo.SOMs) #Spin up a progress bar for each GPO processed Write-Progress -activity "Linking Group Policies to $TargetDomainFQDN" -status "Processed: $j" -percentcomplete -1 } #End of ForEach ($CustomGpo in $CustomGpoInfo)... } #End of If ($SomInfo)... } #End of If ($CustomGpoInfo)... Else { #Log failure to import custom GPO XML object Log-ScriptEvent $NewReport "$CustomGpoXML import failed" "Import_GPOs" 3 Log-ScriptEvent $NewReport "Script execution stopped" "Import_GPOs" 1 Log-ScriptEvent $NewReport ("=" * 90) "Import_GPOs" 1 Write-Error "$CustomGpoXML not found. Script execution stopped." Exit 2 } #End of Else ($CustomGpoInfo)... ##Finish Script (Finish_Script) #Close of the script log Log-ScriptEvent $NewReport " " " " 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 Log-ScriptEvent $NewReport "FILTERS_IMPORTED: $k" "Finish_Script" 1 Log-ScriptEvent $NewReport "POLICIES_PROCESSED: $i" "Finish_Script" 1 Log-ScriptEvent $NewReport "LINKS_UPDATED: $j" "Finish_Script" 1 Log-ScriptEvent $NewReport ("=" * 90) "Finish_Script" 1 } #End of If (New-Item -ItemType File -Path $NewReport)... Else { #Write a custom error and use continue to override silently continue Write-Error "$NewReport not found. Script execution stopped." Exit 1 } #End of Else (New-Item -ItemType File -Path $NewReport)... } #end of function Mirror-ADGpos |