MSDeploy Powershell Scripts Part II – Exceptions And Remote Server Syncs

 

In this blog I will show how to script syncing remote servers and add some useful exception handling.  This blog assumes you have read or are familiar with part I at:

http://blogs.iis.net/jamescoo/archive/2009/09/09/cool-msdeploy-powershell-scripts.aspx

For MSDeploy API examples visit here:

http://blogs.iis.net/jamescoo/archive/2009/11/03/msdeploy-api-scenarios.aspx

First a borrowed function that will help implement a try catch block in the script-let logic from part I.  This will allow for MSDeploy exceptions to be caught and reported making life a bit easier when you get an error.  The code is from:

http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx

Simply paste this into the Powershell session ( or start all these functions in your profile ) and you are ready to use try catch blocks:

function Try
{
    param
    (
        [ScriptBlock]$Command = $(throw "The parameter -Command is required."),
        [ScriptBlock]$Catch   = { throw $_ },
        [ScriptBlock]$Finally = {}
    )
    & {
        $local:ErrorActionPreference = "SilentlyContinue"
        trap
        {
            trap
            {
                & {
                    trap { throw $_ }
                    &$Finally
                }
                throw $_
            }
            $_ | & { &$Catch }
        }
        &$Command
    }

    & {
        trap { throw $_ }
        &$Finally
    }
}

Visiting back to part I, the most versatile script-let was the Sync-Provider so next is to add exception handling to this script-let.  Of course, not to forget to load the assembly first (hopefully this is now part of your Powershell profile ).

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Deployment")
function Sync-Provider($provider, $sourceLocation, $destLocation) 
{ 
    $destBaseOptions   = new-object Microsoft.Web.Deployment.DeploymentBaseOptions 
    $syncOptions       = new-object Microsoft.Web.Deployment.DeploymentSyncOptions 
    Try
    {
      $deploymentObject = [Microsoft.Web.Deployment.DeploymentManager]::CreateObject($provider, $sourceLocation) 
      $deploymentObject.SyncTo($provider,$destLocation,$destBaseOptions,$syncOptions)
    }
    Catch
    {
      echo "EXCEPTION THROWN::[ $_ ] "
      #throw $_
    }
} 

 

Now that you have better exception handling you can get messages like this ( in this case you cannot sync a site to a directory with apphostconfig provider ):

Sync-Provider apphostconfig "Default Web Site" c:\mysite

EXCEPTION THROWN::[ Exception calling "SyncTo" with "4" argument(s): "Invalid site name
" ]

In order to perform remote server syncs, credentials will have to be included with the name of the machines that you want to sync.  To do this I decided to go with the below functions to help store remote machine information.  ‘Create-Server’ will make a new file that will be saved on the local system drive ( feel free to change where you are storing these but you must also change Sync-Server respectively ) with the same name as the computer name being stored.  The credentials will be saved and the password will be converted to a secure and then encrypted into the same file.

#load and run this function for each server you would like to perform a remote sync operation on

function Create-Server()
{
  Try
  {
    $machine = "" | select ComputerName,UserName,Password
    $machine.ComputerName = Read-host -prompt "Machine name"
    $fileName = $machine.ComputerName
    $machine.UserName    = Read-host -prompt "User name"
    $machine.Password = Read-host -prompt Password -assecurestring | convertfrom-securestring
    $machine | Export-Csv $Env:SystemDrive\$fileName.txt
  } Catch {
      echo "EXCEPTION THROWN::[ $_ ] "
  }  
}  

 

 

 

The next function will help us later when converting the secure string to plain text, this snippet is from a forum reply at:

http://www.vistax64.com/powershell/159190-read-host-assecurestring-problem.html

Function ConvertTo-PlainText( [security.securestring]$secure )

{
   $marshal = [Runtime.InteropServices.Marshal]
   $marshal::PtrToStringAuto( $marshal::SecureStringToBSTR($
secure) )
}

The next function Sync-Server will take a provider as a string, then the name of two machines that you want to sync.  It will then match the names of the machines to corresponding files on the local box and import the original objects that are exported in “Create-Server”, then convert the encrypted password so it can be used by msdeploy.  Examples of usage are farther down:

function Sync-Server($provider, $source, $dest)
{
  Try
  {
    #read from files
    $sourceMachine = Import-Csv $Env:SystemDrive\$source.txt
    $destMachine = Import-Csv $Env:SystemDrive\$dest.txt
  }
  Catch
  {
    echo "EXCEPTION THROWN::[ $_  ] "
  }
    $destBaseOptions   = new-object Microsoft.Web.Deployment.DeploymentBaseOptions
    $sourceBaseOptions   = new-object Microsoft.Web.Deployment.DeploymentBaseOptions
    $syncOptions = new-object Microsoft.Web.Deployment.DeploymentSyncOptions

    #fill in remoting information for source machine
    $sourceBaseOptions.ComputerName = $sourceMachine.ComputerName
    $sourceBaseOptions.UserName = $sourceMachine.UserName;
    $password = ConvertTo-SecureString $sourceMachine.Password
    $password = ConvertTo-PlainText $password
    $sourceBaseOptions.Password = $password

    #fill in remoting information for destination machine
    $destBaseOptions.ComputerName = $destMachine.ComputerName
    $destBaseOptions.UserName = $destMachine.UserName;
    $password = ConvertTo-SecureString $destMachine.Password
    $password = ConvertTo-PlainText $password
    $destBaseOptions.Password = $password

  Try
  {
    $providerOptions = new-object Microsoft.Web.Deployment.DeploymentProviderOptions($provider)
    $deploymentObject = [Microsoft.Web.Deployment.DeploymentManager]::CreateObject($providerOptions, $sourceBaseOptions)
    $deploymentObject.SyncTo($destBaseOptions,$syncOptions)
  }
  Catch
  {
    echo "EXCEPTION THROWN::[ $_  ] "
  }
}

Assuming you have all of the above code snippets now loaded into a Powershell session here is an example of the use of these methods to accomplish a server sync:

PS > Create-Server
Machine name: <machineName1>
User name: <userName>
Password: ********
PS > Create-Server
Machine name: <machineName2>
User name: <userName>
Password: ********

PS > Sync-Server metakey <machineName1> <machineName2>

Errors           : 0
Warnings         : 0
BytesCopied      : 0
ObjectsDeleted   : 0
ObjectsUpdated   : 0
ObjectsAdded     : 0
TotalChanges     : 0
ParameterChanges : 0

Sometimes it takes a little while for the sync to occur, and in the above example I had installed MSDeploy and started the service on my remote machines.  Also, you can modify the $sourceBaseOptions and $destBaseOptions by changing the “TempAgent” property to true.  This can help you run remotely when you have not installed MSDeploy on your source or destination.  TempAgent is very useful for this type of syncing. 

Another note is that if you want to take this one step farther, you can make a couple of changes and sync a provider path across both machines, I will just paste the new function farther down and you can use it like this:

Sync-ServerPath apphostconfig “Default Web Site” <machine1> <machine2>

function Sync-ServerPath($provider,$path, $source, $dest)
{
  Try
  {
    #read from files
    $sourceMachine = Import-Csv $Env:SystemDrive\$source.txt
    $destMachine = Import-Csv $Env:SystemDrive\$dest.txt
  }
  Catch
  {
    echo "EXCEPTION THROWN::[ $_  ] "
  }
    $destBaseOptions   = new-object Microsoft.Web.Deployment.DeploymentBaseOptions
    $sourceBaseOptions   = new-object Microsoft.Web.Deployment.DeploymentBaseOptions
    $syncOptions = new-object Microsoft.Web.Deployment.DeploymentSyncOptions

    #fill in remoting information for source machine
    $sourceBaseOptions.ComputerName = $sourceMachine.ComputerName
    $sourceBaseOptions.UserName = $sourceMachine.UserName;
    $password = ConvertTo-SecureString $sourceMachine.Password
    $password = ConvertTo-PlainText $password
    $sourceBaseOptions.Password = $password

    #fill in remoting information for destination machine
    $destBaseOptions.ComputerName = $destMachine.ComputerName
    $destBaseOptions.UserName = $destMachine.UserName;
    $password = ConvertTo-SecureString $destMachine.Password
    $password = ConvertTo-PlainText $password
    $destBaseOptions.Password = $password

  Try
  {
    $deploymentObject = [Microsoft.Web.Deployment.DeploymentManager]::CreateObject($provider,$path, $sourceBaseOptions)
    $deploymentObject.SyncTo($provider,$path,$destBaseOptions,$syncOptions)
  }
  Catch
  {
    echo "EXCEPTION THROWN::[ $_  ] "
  }
}

Thanks for reading and hope this is useful.  Please leave replies for any request / corrections or comments.  In part III, I will try to go into things like using Credentials Manager, dumps or other verbs like getdependencies or getsysteminfo.

1 Comment

  • Hi James,

    Thank you for the detailed post on calling the Web Deployment API from Powershell. I'm trying to run

    Sync-ServerPath() function on my machine (Windows 7 x 64 + PowerShell 2.0) and I'm getting the following exception:

    EXCEPTION THROWN::[ Exception calling "SyncTo" with "4" argument(s): "Unable to cast object of type 'Microsoft.Web.Deployment.DeploymentChangeSummary' to type 'Microsoft.Web.Deployment.DeploymentChange Summary'."

    What could be the reason for such cast exception? Have you seen this error on your end?

Comments have been disabled for this content.