Snapshot.psm1
#region Get-ActiveDirectorySnapshot Function Get-ActiveDirectorySnapshot { <# .Synopsis Get the Active Directory snapshots. .DESCRIPTION Get the Active Directory snapshots on a domain controller. .EXAMPLE PS C:\> Get-ActiveDirectorySnapshot Identity Date SetID -------- ---- ----- {B22E56F9-C4C5-49C2-A4EF-67A40DE21132} 7/9/2016 9:08:54 μμ {34987893-9D63-4955-968B-3000002789F5} {1DF443D2-9F27-41C4-9E32-950F557E9826} 7/9/2016 8:39:35 μμ {6D3802A3-BD22-4732-AE27-5FEC25EE1E01} #> [cmdletBinding()] Param ( # The identity of the snapshot [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [string]$Identity, # The Active Directory Domain Controller [Parameter(Mandatory=$false, Position=1)] [string[]]$Server = $env:COMPUTERNAME ) Begin {} Process { # Get the snapshots foreach($s in $Server) { $snapshots = Get-WmiObject Win32_ShadowCopy -ComputerName $Server | Select-Object @{Name="Identity"; Expression={$_.Id}}, ` @{Name="Date"; Expression={$_.ConvertToDateTime($_.InstallDate)}}, ` @{Name="SetID"; Expression={$_.SetID}}, ` @{Name="Mounted"; Expression={$_.ExposedLocally}}, ` @{Name="MountPoint"; Expression={$_.ExposedName}}, ` @{Name="Server"; Expression={$s}} | Sort-Object -Property Install_Date -Descending # Return particular snapshot (if requested) if($Identity) { $snapshots | Where-Object {$_.Identity -eq $Identity} } else { $snapshots } } } End {} } #endregion #region New-ActiveDirectorySnapshot Function New-ActiveDirectorySnapshot { <# .Synopsis Create an Active Directory snapshot. .DESCRIPTION Create an Active Directory snapshot. .EXAMPLE PS C:\> New-ActiveDirectorySnapshot Identity Date SetID Server -------- ---- ----- ------ {809B678A-7733-4011-ACD5-6... 17/6/2017 12:31:54 πμ {D69CF349-6EFD-4717-AB58-1... DC1 #> [cmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( # The Active Directory Domain Controller [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [string[]]$Server = $env:COMPUTERNAME ) Begin {} Process { # Select the Domain Controller if(-Not $Server) { $Server = $env:COMPUTERNAME } foreach($s in $Server) { if($PSCmdlet.ShouldProcess($s, "Create Active Directory snapshot")) { $id = Invoke-Command -ComputerName $s ` -ScriptBlock { $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = "ntdsutil" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.RedirectStandardInput = $true $pinfo.UseShellExecute = $false $pinfo.CreateNoWindow = $true $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $p.StandardInput.WriteLine("Activate Instance NTDS") $p.StandardInput.WriteLine("snapshot") $p.StandardInput.WriteLine("create") $p.StandardInput.WriteLine("quit") $p.StandardInput.WriteLine("quit") $p.WaitForExit() $stdout = $p.StandardOutput.ReadToEnd() $stderr = $p.StandardError.ReadToEnd() if($p.ExitCode -ne 0) { Write-Error $stderr return $null } else { [string]$data = $stdout $startIndex = $data.IndexOf("Snapshot set") + 13 $stopIndex = $data.IndexOf("generated") - 1 $data = $data.Substring($startIndex, $stopIndex - $startIndex) $data } } if($id -ne $null) { Get-ActiveDirectorySnapshot -Server $s | Where-Object {$_.SetID -eq $id} } else { Write-Error "Failed to create Active Directory snapshot on server $s" } } } } End {} } #endregion #region Remove-ActiveDirectorySnapshot Function Remove-ActiveDirectorySnapshot { <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')] Param ( # The identity of the snapshot to remove. [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [string[]]$Identity, # The Active Directory Domain Controller. [Parameter(Mandatory=$false, Position=1)] [string]$Server = $env:COMPUTERNAME ) Begin {} Process { foreach($i in $Identity) { if($PSCmdlet.ShouldProcess($i, "Remove Active Directory snapshot")) { $snapshot = Get-WmiObject Win32_ShadowCopy -ComputerName $Server | Where-Object {$_.Id -eq $Identity} if($snapshot) { $snapshot.Delete() } else { Write-Error "Could not find snaphot!" } } } } End { } } #endregion #region Mount-ActiveDirectorySnapshot function Mount-ActiveDirectorySnapshot { <# .Synopsis Mount an Active Directory snapshot. .DESCRIPTION Mount an Active Directory snapshot and use dsamain.exe to load it. .EXAMPLE PS C:\> Get-ActiveDirectorySnapshot Identity : {CAE74093-6422-42D5-AB1B-988C418E1560} Date : 17/6/2017 11:42:37 πμ SetID : {7772C6CE-F375-49B9-BC28-16F965B4E55C} Mounted : False MountPoint : Server : DC1 PS C:\> Mount-ActiveDirectorySnapshot -Identity "{CAE74093-6422-42D5-AB1B-988C418E1560}" -Port 33389 Mount the Active Directory snapshot with id {CAE74093-6422-42D5-AB1B-988C418E1560} on port 33389 .EXAMPLE PS C:\> Get-ActiveDirectorySnapshot | Sort-Object -Property Date -Descending | Select-Object -First 1 | Mount-ActiveDirectorySnapshot -Port 33389 -Verbose VERBOSE: Performing the operation "Mount Active Directory snapshot" on target "{CAE74093-6422-42D5-AB1B-988C418E1560}". VERBOSE: Loading database: C:\$SNAP_201706171142_VOLUMEC$\Windows\NTDS\ntds.dit VERBOSE: Mounting database on port 33389 VERBOSE: In order to dismount the database, close dsamain and then run "Dismount-ActiveDirectorySnapshot Mount the latest Active Directory snapshot. #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( # The identity of the snapshot to mount [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNull()] [string] $Identity, # The port to use in order to query the mounted database [Parameter(Mandatory=$true, Position=1)] [ValidateNotNull()] [ValidateRange(1025,65535)] [int] $Port ) Begin {} Process { if($PSCmdlet.ShouldProcess($Identity, "Mount Active Directory snapshot")) { # Mount the snapshot $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = "ntdsutil" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.RedirectStandardInput = $true $pinfo.UseShellExecute = $false $pinfo.CreateNoWindow = $true $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $p.StandardInput.WriteLine("Activate Instance NTDS") $p.StandardInput.WriteLine("snapshot") $p.StandardOutput.DiscardBufferedData() $p.StandardInput.WriteLine("mount $Identity") $p.StandardInput.WriteLine("quit") $p.StandardInput.WriteLine("quit") $p.WaitForExit() $stdout = $p.StandardOutput.ReadToEnd() $stderr = $p.StandardError.ReadToEnd() if($p.ExitCode -ne 0) { Write-Error $stderr return $null } else { # Get the snapshot mount directory $mountPoint = (Get-ActiveDirectorySnapshot -Identity $Identity).MountPoint # Add the path to the database file $mountPoint += (Get-Item -Path "HKLM:\SYSTEM\CurrentcontrolSet\Services\NTDS\Parameters").GetValue("DSA Database File").SubString(3) } if($mountPoint) { Write-Verbose "Loading database: $mountPoint" # Create a background job and start dsamain Write-Verbose ("Mounting database on port $Port") # Start DSAMAIN $DSAMAIN = Start-Process -FilePath dsamain.exe -ArgumentList "-dbpath $mountPoint -ldapport $Port" -PassThru Write-Verbose ('In order to dismount the database, close dsamain and then run "Dismount-ActiveDirectorySnapshot') } else { Write-Error "Could not mount the database." } } } End {} } #endregion #region Dismount-ActiveDirectorySnapshot function Dismount-ActiveDirectorySnapshot { <# .Synopsis Dismount an Active Directory snapshot. .DESCRIPTION Dismount and Active Directory snapshot. .EXAMPLE PS C:\> Dismount-ActiveDirectorySnapshot -Identity "{CAE74093-6422-42D5-AB1B-988C418E1560}" -Verbose VERBOSE: Performing the operation "Dismount Active Directory snapshot" on target "{CAE74093-6422-42D5-AB1B-988C418E1560}". Dismount the Active Directory snapshot with id {CAE74093-6422-42D5-AB1B-988C418E1560} .EXAMPLE PS C:\> Get-ActiveDirectorySnapshot | ? mounted -eq $true | Dismount-ActiveDirectorySnapshot -Verbose VERBOSE: Performing the operation "Dismount Active Directory snapshot" on target "{CAE74093-6422-42D5-AB1B-988C418E1560}". Dismount all currently mounted Active Directory snapshots. #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( # The identity of the snapshot to mount [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNull()] [string] $Identity ) Begin {} Process { if($PSCmdlet.ShouldProcess($Identity, "Dismount Active Directory snapshot")) { # Dismount the snapshot $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = "ntdsutil" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.RedirectStandardInput = $true $pinfo.UseShellExecute = $false $pinfo.CreateNoWindow = $true $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $p.StandardInput.WriteLine("Activate Instance NTDS") $p.StandardInput.WriteLine("snapshot") $p.StandardOutput.DiscardBufferedData() $p.StandardInput.WriteLine("unmount $Identity") $p.StandardInput.WriteLine("quit") $p.StandardInput.WriteLine("quit") $p.WaitForExit() $stdout = $p.StandardOutput.ReadToEnd() $stderr = $p.StandardError.ReadToEnd() if($p.ExitCode -ne 0) { Write-Error $stderr return $null } else { } } } End {} } #endregion #region Compare-ActiveDirectorySnapshotObject function Compare-ActiveDirectorySnapshotObject { <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE PS C:\> $guid = (Get-ADUser cpolydorou).ObjectGUID PS C:\> Compare-ActiveDirectorySnapshotObject -ObjectGUID $guid -ProductionServer localhost -SnapshotServer localhost:33389 -Attributes SamAccountName,GivenName,SN Attribute ProductionValue SnapshotValue Status --------- --------------- ------------- ------ DistinguishedName CN=Christos Polydorou,OU=U... CN=Christos Polydorou,OU=U... Equal GivenName Christos Christos Equal Name Christos Polydorou Christos Polydorou Equal ObjectClass user user Equal ObjectGUID 241a6ed9-832e-458b-ab77-c8... 241a6ed9-832e-458b-ab77-c8... Equal SamAccountName cpolydorou cpolydorou Equal SN Polydorou 2 Polydorou Different Compare the values of SamAccountName, GivenName and SN for the user cpolydorou using the object's ObjectGUID #> [CmdletBinding()] Param ( # The identity of the object to compare [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNullOrEmpty()] [string[]] $ObjectGUID, # The production Domain Controller [Parameter(Mandatory=$true, Position=1)] [ValidateNotNullOrEmpty()] [string] $ProductionServer, # The snapshot Domain Controller [Parameter(Mandatory=$true, Position=2)] [ValidateNotNullOrEmpty()] [string] $SnapshotServer, # The attributes to load [Parameter(Mandatory=$false, Position=3)] [string[]] $Attributes = "*" ) Begin { # Load the ActiveDirectory module try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Throw "Could not load the ActiveDirectory module." } } Process { foreach($o in $ObjectGUID) { # Get the object from the production DC $productionObject = Get-ADObject -Filter {objectguid -eq $o} -Properties $Attributes -Server $ProductionServer # Check if the object exists if( -not $productionObject) { Write-Error "Could not find an object with GUID $o in production server." } # Get the object from the snapshot $snapshotObject = Get-ADObject -Filter {objectguid -eq $o} -Properties $Attributes -Server $SnapshotServer # Check if the object exists if(-not $snapshotObject) { Write-Error "Could not find an object with GUID $o in snapshot server." } Compare-ActiveDirectoryObject -ReferenceObject $productionObject -DifferenceObject $snapshotObject | Select-Object -Property @{Name = "Attribute"; Expression = {$_.PropertyName}}, ` @{Name = "ProductionValue"; Expression = {$_.ReferenceValue}}, ` @{Name = "SnapshotValue"; Expression = {$_.DifferenceValue}}, ` Status } } End {} } #endregion #region Restore-ActiveDirectoryAttribute function Restore-ActiveDirectoryAttribute { <# .Synopsis Restore Active Directory attributes. .DESCRIPTION Restore Active Directory object attributes using an Active Directory snapshot .EXAMPLE PS C:\Windows\system32> $guid = (Get-ADUser cpolydorou).ObjectGUID PS C:\Windows\system32> Restore-ActiveDirectoryAttribute -ObjectGUID $guid -ProductionServer localhost -SnapshotServer localhost:33389 -Attribute sn,givenname -Verbose VERBOSE: Performing the operation "Restore attribute sn" on target "CN=Christos Polydorou,OU=Users,OU=LAB,DC=LAB,DC=local". VERBOSE: Performing the operation "Restore attribute givenname" on target "CN=Christos Polydorou,OU=Users,OU=LAB,DC=LAB,DC=local". Restore the sn and givenname attributes for user "cpolydorou" #> [CmdletBinding(SupportsShouldProcess=$true, PositionalBinding=$false, ConfirmImpact='High')] Param ( # The ObjectGUID of the Active Directory object [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNullOrEmpty()] [string] $ObjectGUID, # The attribute to restore [Parameter(Mandatory=$true, Position=1)] [ValidateNotNullOrEmpty()] [string[]] $Attribute, # The production server [Parameter(Mandatory=$true, Position=2)] [ValidateNotNullOrEmpty()] [string] $ProductionServer, # The snapshot server [Parameter(Mandatory=$true, Position=3)] [ValidateNotNullOrEmpty()] [string] $SnapshotServer ) Begin { # Import the ActiveDirectory module try { Import-Module ActiveDirectory -ErrorAction Stop -Verbose:$false } catch { Throw "Could not load the ActiveDirectory module." } } Process { foreach($o in $ObjectGUID) { # Get the object from the production DC $productionObject = Get-ADObject -Filter {objectguid -eq $o} -Properties $Attribute -Server $ProductionServer # Check if the object exists if( -not $productionObject) { Write-Error "Could not find an object with GUID $o in production server." continue } # Get the object from the snapshot $snapshotObject = Get-ADObject -Filter {objectguid -eq $o} -Properties $Attribute -Server $SnapshotServer # Check if the object exists if(-not $snapshotObject) { Write-Error "Could not find an object with GUID $o in snapshot server." continue } # Restore the attributes foreach($a in $Attribute) { # TODO: Check if the attribute exists if($productionObject.PropertyNames -notcontains $a) { Write-Error "Attribute $a could not be found." } # Set the value on the production if ($pscmdlet.ShouldProcess($productionObject.DistinguishedName, "Restore attribute $a")) { # Get the value from the snapshot object $value = $snapshotObject | % $a # Form the hashtable $table = @{ "$a" = "$value"} Set-ADObject -Identity $o ` -Server $ProductionServer ` -Confirm:$false ` -replace $table } } } } End {} } #endregion #region Exports Export-ModuleMember -Function Get-ActiveDirectorySnapshot Export-ModuleMember -Function New-ActiveDirectorySnapshot Export-ModuleMember -Function Remove-ActiveDirectorySnapshot Export-ModuleMember -Function Mount-ActiveDirectorySnapshot Export-ModuleMember -Function Dismount-ActiveDirectorySnapshot Export-ModuleMember -Function Compare-ActiveDirectorySnapshotObject Export-ModuleMember -Function Restore-ActiveDirectoryAttribute #endregion |