# Copyright (c) Microsoft Corporation. All rights reserved. # ################################################################################# # # The sample scripts are not supported under any Microsoft standard support # program or service. The sample scripts are 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 scripts # and documentation remains with you. In no event shall Microsoft, its authors, or # anyone else involved in the creation, production, or delivery of the scripts 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 scripts or documentation, # even if Microsoft has been advised of the possibility of such damages # ################################################################################# # # Synopsis: Exports CAS configuration to an XML file. # # Usage: # # .\exportcasconfig -cloneConfigData:"C:\cloneConfigData.xml" # -Key:"A9ABA4D2C21C4bc58B303EA47BBE3608" (32 byte string used # for password encryption/decryption) # param($cloneConfigData, $key = "A9ABA4D2C21C4bc58B303EA47BBE3608") ############################################################################# # Logs the information to the cloneLogFile.log # isHost : If true Write the message to the host, inaddition to the log file. # message : The message string to be logged. ############################################################################# function Write-Information([Boolean] $isHost, [String] $message) { # Log all the messages to the log file. if($logger -ne $null) { $logger.WriteLine(); $logger.WriteLine($message) } # Write the error message to the host also. if($isHost -eq $true) { write-host $message } } ########################################################################### # The function adds some useful information into the log file # before the actual Export process starts. ########################################################################### function Write-LogStartInformation() { $startTime = ([System.DateTime]::Now).ToString() Write-Information $false "************ BEGIN EXPORT PROCESS **************" Write-Information $false $startTime } ############################################################# # Releases the file handles used by the Export Script. ############################################################# function Export-ReleaseHandles() { write-debug "Export-ReleaseHandles" if($logger -ne $null) { Write-Information $false "************ END EXPORT PROCESS **************" $logger.flush() $logger.close() } if($xmlWriter -ne $null) { $xmlWriter.Close() } } ############################################################### # Finds the parameters of the objects the command returns. Gets # the objects using the command, iterates through the parameters # of each object and creates the XML component for each object. # getCommand: The command to be run # writer: Writer to write exported data to ############################################################### function ExportObjects([String] $getCommand, [System.Xml.XmlTextWriter] $writer) { write-debug ("ExportObjects " + $getCommand) # Get all the object we need to export using the given command. # The command may return: # - null if there are no objects to export # - single object # - array of objects. $resultObjects = invoke-expression $getCommand $resultObjectsCount = 0 # If no objects returned Log and return. if (!$resultObjects) { Write-Information $false "$getCommand returned 0 objects" return } # If we are here, there is at least one object to export. # First, extract the type of objects being exported. $objectType = GetCommandNoun $getCommand foreach ($object in $resultObjects) { # If we have not done it yet, get all the object properties using the # Get-Member cmdlet. if ($objectPropertyNames -eq $null) { $objectPropertyNames = Get-Member -InputObject $object -MemberType Property } # Create a new XML element for the current object. $writer.WriteStartElement($objectType) # Save all the properties. foreach ($prop in $objectPropertyNames) { if ($object.($prop.Name) -eq $null) { $writer.WriteElementString($prop.Name, '$null') } elseif ($prop.Definition.ToString().ToUpper().Contains("MULTIVALUEDPROPERTY")) { if ($object.($prop.Name).Count -eq 0) { $writer.WriteElementString($prop.Name, '$null') continue; } $multiValuedDataItem = $null foreach($multiValuedItem in $object.($prop.Name)) { if ($multiValuedDataItem -ne $null) { $multiValuedDataItem = $multiValuedDataItem + ", " } $multiValuedDataItem = $multiValuedDataItem + "'" + $multiValuedItem.ToString() + "'" } $writer.WriteElementString($prop.Name, $multiValuedDataItem) } elseif ($prop.Definition.ToString().ToUpper().Contains("SYSTEM.MANAGEMENT.AUTOMATION.MSHCREDENTIAL")) { # Create a new XML element for the current Parameter. $writer.WriteStartElement($prop.Name) # Convert the PSCredential into a clear text "Domain\Username" string and # a "Password" string encrypted with the Key parameter $credential = $object.($prop.Name) $securePassword = $credential.Password # $securePassword = ConvertTo-SecureString -String $encryptedPassword -key $encryptionKey $encryptedPassword = ConvertFrom-SecureString -SecureString $securePassword -key $encryptionKey $writer.WriteElementString("UserName", $credential.UserName) $writer.WriteElementString("Password", $encryptedPassword) write-debug ("credential.UserName=" + $Credential.UserName) write-debug ("credential.Password=" + $Credential.GetNetworkCredential().Password) # Finish the Parameter element. $writer.WriteEndElement() } else { $writer.WriteElementString($prop.Name, $object.($prop.Name)) } } # Finish the element. $writer.WriteEndElement() # Increment the Objects Count counter. $resultObjectsCount++ } Write-Information $false "$getCommand returned $resultObjectsCount objects" return } ############################################ # Extract a noun from a given command # command: The command to be parsed for noun # returns the noun part of the command. ############################################ function GetCommandNoun([String] $command) { write-debug "GetCommandNoun" # Take the part that goes after '-' and if there are any parameters then # cut them off. $nounIndex = $command.IndexOf('-') + 1 $nounEndIndex = $command.IndexOf(' ', $nounIndex) if ($nounEndIndex -eq -1) { $nounEndIndex = $command.Length } $nounLength = $nounEndIndex - $nounIndex return $command.Substring($nounIndex, $nounLength) } ################################################################ # Return the list of items to clone # returns: List of items to be cloned. ################################################################ function ReadCloneItems() { write-debug "ReadCloneItems" $cloneItems = @( "Get-OwaVirtualDirectory -server $Machinename", "Get-ActiveSyncVirtualDirectory -server $Machinename", "Get-OutlookAnywhere -server $Machinename", "Get-UMVirtualDirectory -server $Machinename", "Get-AutodiscoverVirtualDirectory -server $Machinename", "Get-WebServicesVirtualDirectory -server $Machinename", "Get-OabVirtualDirectory -server $Machinename", "Get-ClientAccessServer -identity $Machinename", "Get-PopSettings", "Get-ImapSettings" ) return $cloneItems } ###################################################### # Retrieves the Root setup registry entry. # returns: return entry value of found else null ###################################################### function GetCASInstallPath() { write-debug "GetCASInstallPath" # Get the root setup entires. $setupRegistryPath = "HKLM:\SOFTWARE\Microsoft\Exchange\Setup" $setupEntries = get-itemproperty $setupRegistryPath if($setupEntries -eq $null) { return $null } # Try to get the Install Path. $installPath = $setupEntries.MsiInstallPath return $installPath } ###################################################### # Retrieves the Setup Registry value of the given entry. # setupProperty: Setup Setting to the retrieved # returns: return entry value of found else null ###################################################### function GetCASScriptsPath([String] $scriptFileName) { write-debug "GetCASScriptsPath" # Append the script path with file name. $scriptFilePath = $installPath + "Scripts\" + $scriptFileName return $scriptFilePath } ################################################################ # Validates the input parameters. # returns: True if validation succeeds else False. ################################################################ function ValidateInput() { write-debug "ValidateInput" if($cloneConfigData -eq $null) { write-debug "ValidateInput" Write-Information $true "Input parameters are not set" return $false } return $true } ############################################################################# # InitializationCheck # Setup script variables, check arguments etc. ############################################################################# function InitializationCheck() { write-debug "InitializationCheck" # Global variable for storing handle to the clone log file. # Change this to Actual Path relative to [C:\Program Files\Microsoft\Exchange Server\] $script:success = $false $script:installPath = GetCASInstallPath $script:logfilePath = $installPath + "Logging\SetupLogs\cloneLogFile.log" $script:logger = new-object System.IO.StreamWriter ($logfilePath , $true) if ($key -eq $uniqueKey) { write-host -ForegroundColor "red" "WARNING: Passwords will be encrypted with a default script encryption key" } # Check/Setup the SecurePassword encryption key if (($key.Length -ne 32) -and ($key.Length -ne 24) -and ($key.Length -ne 16)) { Write-Information $true "Key Length needs to be 16, 24 or 32 bytes long." return } $script:encryptionKey = $key.ToCharArray() write-debug ("encryption key=" + $encryptionKey) Write-LogStartInformation # Return error if any extra parameters are supplied. if($script:args.Count -gt 0) { foreach ($arg in $script:args) { Write-Information $true "Invalid additional parameter $arg passed." } return } # Initialize "global" variables that can be used in the template. $script:MachineName = [System.Environment]::MachineName $script:casserver = get-ExchangeServer -Identity:$MachineName | where { $_.IsClientAccessServer -eq $true } if (!$script:casserver) { Write-Information $true "Please run the Export script on the Client Access Server." return } $isValidInput = ValidateInput if ($isValidInput -eq $false) { return } $script:success = $true } ############################################################################# # Main Script starts here, validates the parameters. Defines the list of # cloneable items. Gets the data corresponding # to each item and adds them to the clone data file. ############################################################################# #Usage #:: ./exportcasconfig #-cloneConfigData:"C:\cloneConfigData.xml" # Do all the Initialization Checks InitializationCheck if (-not $success) { Export-ReleaseHandles exit } write-debug "Main" # Create and initialize XML text writer. $xmlWriter = new-object System.Xml.XmlTextWriter( $cloneConfigData, [System.Text.Encoding]::UTF8) $xmlWriter.Formatting = [System.Xml.Formatting]::Indented $xmlWriter.WriteStartDocument() $xmlWriter.WriteStartElement("ExportedCASConfiguration") # Read all get commands that we want to use for exporting configuration data $getCommands = ReadCloneItems # Export each category of objects. foreach ($getCommand in $getCommands) { ExportObjects $getCommand $xmlWriter } # Close the XML file. $xmlWriter.WriteEndElement() $xmlWriter.WriteEndDocument() $xmlWriter.Close() # Exception handling. trap { Write-Information $true "Exporting CAS configuration information failed." Write-Information $true ("Reason: " + $error[0]) Export-ReleaseHandles exit } Write-Information $true "CAS configuration is exported successfully to $cloneConfigData" Export-ReleaseHandles