Continuing the work on backing up Exchange configurations, I decided to simplify the script to back up the configurations to this:
$su = get-credential $Target_Directory = 'C:\testfiles\Ex_ConfigExport' $ExchangeServer = '006Exch-Srv1' $SessionParams = @{ ConfigurationName = 'MicroSoft.Exchange' ConnectionURI = "http://$ExchangeServer/powershell/" Authentication = 'Kerberos' ErrorAction = 'Stop' Credential = $su } $ExSession = New-PSSession @SessionParams $Ex_Object_Folders = @{ Server = @( 'MailboxServer', 'TransportServer', 'ClientAccessServer', 'UMServer' ) UM = @( 'UMDialPlan' 'UMAutoAttendant' 'UMIPGateway' 'UMHuntGroup' ) Transport = @( 'TransportRule', 'JournalRule', 'SendConnector', 'ReceiveConnector', 'ForeignConnector', 'RoutingGroupConnector', 'AcceptedDomain', 'RemoteDomain' ) Database = @( 'MailboxDatabase', 'PublicFolderDatabase', 'DatabaseAvailabilityGroup' ) Policy = @( 'ActiveSyncMailboxPolicy', 'AddressBookPolicy', 'EmailAddressPolicy', 'ManagedFolderMailboxPolicy', 'OwaMailboxPolicy', 'RetentionPolicy', 'RetentionPolicyTag', 'RoleAssignmentPolicy', 'SharingPolicy', 'ThrottlingPolicy', 'UMMailboxPolicy' ) } #Export set folder creation $DirParams = @{ ItemType = 'Directory' Verbose = $true ErrorAction = 'Stop' } Write-Verbose "Checking target root folder" if ( -not ( Test-Path $Target_Directory ) ) { New-Item @DirParams -Path $Target_Directory } $Export_Set = (get-date).tostring('yyyy-MM-dd_HH.mm.ss') $Export_Set_Path = "$Target_Directory\$Export_Set" Write-Verbose "Creating folder for this export set ($Export_Set)" New-Item @DirParams -Path $Export_Set_Path Write-Verbose "Exporting Exchange configuration objects" foreach ($Ex_Object_Folder in $Ex_Object_Folders.keys) { $Ex_Object_Folder_Path = "$Export_Set_Path\$Ex_Object_Folder" New-Item @DirParams -Path $Ex_Object_Folder_Path foreach ( $Ex_Object_Type in $Ex_Object_Folders.$Ex_Object_Folder ) { $Ex_Object_Type_Path = "$Ex_Object_Folder_Path\$Ex_Object_Type" New-Item @DirParams -Path $Ex_Object_Type_Path $SB = [ScriptBlock]::Create("Get-$Ex_Object_Type") Invoke-Command -ScriptBlock $SB -Session $ExSession | foreach { $_ | Export-Clixml "$Ex_Object_Type_Path\$($_.guid).clixml" -Verbose } } } Remove-PSSession $ExSession
Again, right now this is in a testing phase and you’ll need to modify the server name and target directory, and you can remove the credential prompt and session parameter if it’s not necessary in your environment.
Running will create a set of exported configuration objects each time it’s run. Now I want to be able to compare one set to another to see what’s changed. This script will compare two of those configuration sets, and create objects that detail the objects and properties where it finds differences.
<# .Synopsis Compare two export object sets .DESCRIPTION Compares deserialized objects from two directories of .clixml files. #> [CmdletBinding()] Param ( # First export object set directory [Parameter(Mandatory=$true)] $ExportSetPath1, # Second export object set directory [Parameter(Mandatory=$true)] $ExportSetPath2 ) Begin { $CompareSetTimestamps = Get-Item $ExportSetPath1,$ExportSetPath2 | Select FullName,LastWriteTime | Sort LastWriteTime $ReferenceSetPath = $CompareSetTimestamps[0].FullName $DifferenceSetPath = $CompareSetTimestamps[1].FullName $ExcludedProperties = @( 'PSShowComputerName', 'RunspaceId', 'OriginatingServer', 'WhenChanged' ) $ExcludeRegex = [regex]($ExcludedProperties -join '|') } Process { } End { $ReferenceSetFolders = Get-ChildItem $ReferenceSetPath -Directory Write-Verbose "Found $($ReferenceSetFolders.count) Export set folders" foreach ( $ReferenceSetFolder in $ReferenceSetFolders ) { Write-Verbose "***** Comparing $ReferenceSetFolder Objects*****`n" $ReferenceObjectTypeFolders = Get-ChildItem $ReferenceSetFolder.FullName -Directory foreach ( $ReferenceObjectTypeFolder in $ReferenceObjectTypeFolders ) { Write-Verbose " *****Comparing $ReferenceObjectTypeFolder Objects*****`n" $ReferenceObjectFiles = Get-ChildItem $ReferenceObjectTypeFolder.FullName -File $DifferenceObjectTypeFolderPath = @( $DifferenceSetPath, $ReferenceSetFolder, $ReferenceObjectTypeFolder ) -join '\' $DifferenceObjectFiles = Get-ChildItem $ReferenceObjectTypeFolder.FullName -File $AddedObjectFiles = $DifferenceObjectFiles | Where { $ReferenceObjectFiles.name -notcontains $_.name } $DeletedObjectFiles = $ReferenceObjectFiles | Where { $DifferenceObjectFiles.name -notcontains $_.name } $ComparedObjectFiles = $ReferenceObjectFiles | Where { $DifferenceObjectFiles.name -Contains $_.name } foreach ( $ComparedObjectFile in $ComparedObjectFiles ) { $DifferenceObjectFilePath = "$DifferenceObjectTypeFolderPath\$($ComparedObjectFile.Name)" $DifferenceObject = Import-Clixml $DifferenceObjectFilePath if ($DifferenceObject.WhenChanged -le $ComparedObjectFile.LastWriteTime) { Continue } Write-Verbose " Change detected in $($ReferenceObjectTypeFolder.Name) $($DifferenceObject.Identity)" $ReferenceObject = Import-Clixml $ComparedObjectFile.FullName Write-Debug " Comparing properties of $($ReferenceObject.Identity)" $Properties = $ReferenceObject.psobject.properties.name -notmatch $ExcludeRegex foreach ( $Property in $Properties ) { if ([string]$ReferenceObject.$Property -ne [string]$DifferenceObject.$Property) { Write-Verbose " Found change in property $Property of $($ReferenceObjectTypeFolder.Name) $($ReferenceObject.Identity)`n" Write-verbose "`n`nOld value = $($ReferenceObject.$Property)`n`nNew value = $($DifferenceObject.$Property)`n" [PSCustomObject]@{ Guid = $ReferenceObject.Guid RefExportObjectPath = $ComparedObjectFile.FullName DiffExportObjectPath = $DifferenceObjectFilePath ObjectClass = $ReferenceObject.ObjectClass Identity = $ReferenceObject.Identity Property = $Property RefWhenChanged = $ReferenceObject.WhenChanged RefPropertyValue = $ReferenceObject.$Property DiffWhenChanged = $DifferenceObject.WhenChanged DiffPropertyValue = $DifferenceObject.$Property } } }#end property loop }#end object loop }#end object type folder loop }#end export set folder loop }#end End block
For testing, I’m using this script to select the export sets to compare:
$Exportsets = Get-ChildItem C:\testfiles\Ex_ConfigExport -directory | select -expand fullname | Out-GridView -OutputMode Multiple -Title "Select two export sets to compare." If ($Exportsets.count -ne 2) { Write-Warning "Select two (and only two) export sets to compare" Return } $changes = C:\scripts\Ex_Backup\Compare-ExportSets.ps1 $Exportsets[0] $Exportsets[1] $changes
I still need to address added and deleted objects between sets, but that’s relatively trivial compared to finding any changes in all the existing objects. This is not intended to provide a complete report, but to create a collection of objects containing the information necessary to produce it and provide enough information to easily investigate further. It’s also useful for doing ad-hoc comparisons of exports sets from arbitrary days.
In my environment it takes about 5.5 MB to save an export set to disk. These are plain-text files, and using NTFS compression on the folder will drop it down to about 3 MB, so for about 100MB of disk space I can keep about a month’s worth.
This is still obviously work in progress, and I welcome an comments or suggestions.
This is as close as it gets to feature complete.
Are you going to add a Restore-Config functionality down the line ?
I’ve thought about it. It would take some testing, but I think that’s do-able.