Functions/Public/Copy-AMWorkflow.ps1
function Copy-AMWorkflow { <# .SYNOPSIS Copies an Automate workflow. .DESCRIPTION Copy-AMWorkflow can copy a workflow within a server. .PARAMETER InputObject The object to copy. .PARAMETER Name The new name to set on the object. .PARAMETER Folder The folder to place the object in. .PARAMETER ConflictAction The action to take if a conflicting object is found on the destination server. .PARAMETER IdSubstitutions A hashtable containing ID mappings between the source and destination server. The ID from the source server object is the key, the destination server is the value. Use this to define mappings of agents/agent groups/folders, or repository objects where the default mapping actions taken by this workflow are not sufficient. .PARAMETER IgnoreServerVersionDifference Ignore server version differences when copying between servers. If a task needs to be copied from the source server to the destination server, Copy-AMTask will still fail. This could result in an incomplete migration. To avoid this, first use the export/import options in the Server Management Console for any tasks in the workflow(s) to be copied. .PARAMETER Connection The server to copy the object to. .INPUTS The following Automate object types can be modified by this function: Workflow .EXAMPLE # Copy workflow "FTP Files to Company A" to "FTP Files to Company B" Get-AMWorkflow "FTP Files to Company A" | Copy-AMWorkflow -Name "FTP Files to Company B" -Folder (Get-AMFolder WORKFLOWS) .LINK https://github.com/AutomatePS/AutomatePS/blob/master/Docs/Copy-AMWorkflow.md #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] $InputObject, [ValidateNotNullOrEmpty()] [string]$Name, [ValidateScript({$_.Type -eq "Folder"})] $Folder, [ValidateSet("CopySourceServerObject","UseDestinationServerObject")] [string]$ConflictAction, [ValidateNotNullOrEmpty()] [Hashtable]$IdSubstitutions = @{}, [switch]$IgnoreServerVersionDifference, [ValidateNotNullOrEmpty()] [ArgumentCompleter([AMConnectionCompleter])] $Connection ) BEGIN { if ($PSBoundParameters.ContainsKey("Connection")) { $Connection = Get-AMConnection -Connection $Connection if (($Connection | Measure-Object).Count -eq 0) { throw "No Automate server specified!" } elseif (($Connection | Measure-Object).Count -gt 1) { throw "Multiple Automate servers specified, please specify one server to copy the workflow to!" } $taskFolder = Get-AMDefaultFolder -Connection $Connection -Type TASKS $conditionFolder = Get-AMDefaultFolder -Connection $Connection -Type CONDITIONS $processFolder = Get-AMDefaultFolder -Connection $Connection -Type PROCESSES Write-Verbose "Caching workflow IDs for server $($Connection.Alias) for ID checking" $workflowCache = Get-AMWorkflow -Connection $Connection $existingIds = @() $existingIds += $workflowCache.ID $existingIds += $workflowCache.Items.ID $existingIds += $workflowCache.Triggers.ID $existingIds += $workflowCache.Links.ID $existingIds += $workflowCache.Variables.ID $copyToAnotherServer = $true } else { $copyToAnotherServer = $false } $substitions = $IdSubstitutions.PSObject.Copy() } PROCESS { foreach ($obj in $InputObject) { if ($obj.Type -eq "Workflow") { if ($copyToAnotherServer) { # Copy from one Automate server to another if ($obj.ConnectionAlias -ne $Connection.Alias) { if ((Get-AMConnection -ConnectionAlias $obj.ConnectionAlias).Version.Major -ne $Connection.Version.Major) { if ($IgnoreServerVersionDifference.IsPresent) { Write-Warning "Server versions do not match, but this is being ignored. This could result in an incomplete migration if tasks fail to migrate!" } else { throw "Source server and destination server are different versions! This module does not support changing task versions." } } if ($PSBoundParameters.ContainsKey("Folder")) { # If folder was specified, validate that it is on the destination server if ($Folder.ConnectionAlias -ne $Connection.Alias) { throw "Folder specified exists on $($Folder.ConnectionAlias), the folder must exist on $($Connection.Name)!" } } else { # If a folder substition was given, use it, otherwise default to the user folder if ($substitions.ContainsKey($obj.ParentID)) { $Folder = Get-AMFolder -ID $substitions[$obj.ParentID] -Connection $Connection } else { $Folder = Get-AMDefaultFolder -Connection $Connection -Type WORKFLOWS } } } } else { # Copy within the same server $Connection = Get-AMConnection -ConnectionAlias $obj.ConnectionAlias if (-not $PSBoundParameters.ContainsKey("Folder")) { # If folder was not specified, place workflow in same folder as source workflow $Folder = Get-AMFolder -ID $obj.ParentID -Connection $obj.ConnectionAlias } } # If a name was not specified, default to using the original objects name (API will automatically append number if there is a naming conflict) if (-not $PSBoundParameters.ContainsKey("Name")) { $Name = $obj.Name } # Create the new workflow object switch ($Connection.Version.Major) { 10 { $copyObject = [AMWorkflowv10]::new($Name, $Folder, $Connection.Alias) } {$_ -in 11,22,23,24} { $copyObject = [AMWorkflowv11]::new($Name, $Folder, $Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # If an object with the same ID doesn't already exist, use the same ID (when copying between servers) if ($copyToAnotherServer -and $obj.ID -notin $existingIds) { $copyObject.ID = $obj.ID } # Copy properties of the source workflow to the new workflow try { $currentObject = Get-AMWorkflow -ID $obj.ID -Connection $obj.ConnectionAlias } catch { Write-Verbose "Could not find workflow with ID $($obj.ID), assuming this is a workflow that was deleted from the server." $currentObject = $obj.PSObject.Copy() } $copyObject.CompletionState = $currentObject.CompletionState $copyObject.Enabled = $currentObject.Enabled $copyObject.LockedBy = $currentObject.LockedBy $copyObject.Notes = $currentObject.Notes # Copy items into new workflow foreach ($item in $currentObject.Items) { switch ($item.ConstructType) { "Evaluation" { # Create a new evaluation object switch ($Connection.Version.Major) { 10 { $newItem = [AMWorkflowConditionv10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newItem = [AMWorkflowConditionv11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Copy properties of the source evaluation object to the new evaluation $newItem.Expression = $item.Expression } "Wait" { # Create a new wait object switch ($Connection.Version.Major) { 10 { $newItem = [AMWorkflowItemv10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newItem = [AMWorkflowItemv11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Copy properties of the source wait object to the new evaluation $newItem.ConstructType = $item.ConstructType } default { # Create the new workflow item (for workflows/tasks/processes) switch ($Connection.Version.Major) { 10 { $newItem = [AMWorkflowItemv10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newItem = [AMWorkflowItemv11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Workflows don't use an agent, so there's no reason to set it if ($item.ConstructType -ne "Workflow") { $newItem.AgentID = $item.AgentID } # If copying to another server if ($obj.ConnectionAlias -ne $Connection.Alias) { # Look for any specified substitions for the agent, use if provided if ($substitions.ContainsKey($item.AgentID)) { $newItem.AgentID = $substitions[$item.AgentID] } # If there is no substition specified for the item if (-not $substitions.ContainsKey($item.ConstructID)) { $idFilterSet = @{Property = "ID"; Operator = "="; Value = $item.ConstructID} switch ($item.ConstructType) { "Workflow" { $sourceItem = Get-AMWorkflow -FilterSet $idFilterSet -Connection $obj.ConnectionAlias # If the source item exists if ($null -ne $sourceItem) { $destinationItem = Get-AMWorkflow -FilterSet $idFilterSet,@{Property = "Name"; Operator = "="; Value = $sourceItem.Name} -Connection $Connection # If the destination item exists if ($null -ne $destinationItem) { # Prompt user for conflict action if (-not $PSBoundParameters.ContainsKey("ConflictAction")) { $options = [System.Management.Automation.Host.ChoiceDescription[]] @("Copy &Source Server Object", "Use &Destination Server Object") $defaultChoice = 0 $answer = $host.UI.PromptForChoice("Import Conflict", "There already is a workflow with the same ID and Name '$($destinationItem.Name)' in the repository.", $Options, $defaultChoice) switch($answer) { 0 { $ConflictAction = "CopySourceServerObject" } 1 { $ConflictAction = "UseDestinationServerObject" } } } switch ($ConflictAction) { "CopySourceServerObject" { # Copy object from source server to destination server $splat = @{ IdSubstitutions = $substitions Connection = $Connection } if ($PSBoundParameters.ContainsKey("Folder")) { $splat.Add("Folder", $Folder) } $newWorkflow = $sourceItem | Copy-AMWorkflow @splat $substitions.Add($item.ConstructID, $newWorkflow.ID) } "UseDestinationServerObject" { # Use existing object in the destination server $substitions.Add($item.ConstructID, $destinationItem.ID) } } } else { # If the destination item does not already exist, create it $splat = @{ IdSubstitutions = $substitions Connection = $Connection } # Only specify the folder if it was specified by the user, allows IdSubstitions to be used in recursive calls if ($PSBoundParameters.ContainsKey("Folder")) { $splat.Add("Folder", $Folder) } $newWorkflow = $sourceItem | Copy-AMWorkflow @splat $substitions.Add($item.ConstructID, $newWorkflow.ID) } } else { # Enter a bogus GUID, will result in an unbuilt item $substitions.Add($item.ConstructID, (New-Guid).Guid) } } "Task" { $sourceItem = Get-AMTask -FilterSet $idFilterSet -Connection $obj.ConnectionAlias # If the source item exists if ($null -ne $sourceItem) { # Check if a folder substition was provided, default to user folder if not if ($substitions.ContainsKey($sourceItem.ParentID)) { $tempTaskFolder = Get-AMFolder -ID $substitions[$sourceItem.ParentID] -Connection $Connection } else { $tempTaskFolder = $taskFolder } $destinationItem = Get-AMTask -FilterSet $idFilterSet,@{Property = "Name"; Operator = "="; Value = $sourceItem.Name} -Connection $Connection # If the destination item exists if ($null -ne $destinationItem) { # Prompt user for conflict action if (-not $PSBoundParameters.ContainsKey("ConflictAction")) { $options = [System.Management.Automation.Host.ChoiceDescription[]] @("Copy &Source Server Object", "Use &Destination Server Object") $defaultChoice = 0 $answer = $host.UI.PromptForChoice("Import Conflict", "There already is a task with the same ID and Name '$($destinationItem.Name)' in the repository.", $Options, $defaultChoice) switch($answer) { 0 { $ConflictAction = "CopySourceServerObject" } 1 { $ConflictAction = "UseDestinationServerObject" } } } switch ($ConflictAction) { "CopySourceServerObject" { # Copy object from source server to destination server $newTask = $sourceItem | Copy-AMTask -Folder $tempTaskFolder -Connection $Connection $substitions.Add($item.ConstructID, $newTask.ID) } "UseDestinationServerObject" { # Use existing object in the destination server $substitions.Add($item.ConstructID, $destinationItem.ID) } } } else { # If the destination item does not already exist, create it $newTask = $sourceItem | Copy-AMTask -Folder $tempTaskFolder -Connection $Connection $substitions.Add($item.ConstructID, $newTask.ID) } } else { # Enter a bogus GUID, will result in an unbuilt item $substitions.Add($item.ConstructID, (New-Guid).Guid) } } "Process" { $sourceItem = Get-AMProcess -FilterSet $idFilterSet -Connection $obj.ConnectionAlias # If the source item exists if ($null -ne $sourceItem) { if ($substitions.ContainsKey($sourceItem.ParentID)) { $tempProcessFolder = Get-AMFolder -ID $substitions[$sourceItem.ParentID] -Connection $Connection } else { $tempProcessFolder = $processFolder } $destinationItem = Get-AMProcess -FilterSet $idFilterSet,@{Property = "Name"; Operator = "="; Value = $sourceItem.Name} -Connection $Connection # If the destination item exists if ($null -ne $destinationItem) { # Prompt user for conflict action if (-not $PSBoundParameters.ContainsKey("ConflictAction")) { $options = [System.Management.Automation.Host.ChoiceDescription[]] @("Copy &Source Server Object", "Use &Destination Server Object") $defaultChoice = 0 $answer = $host.UI.PromptForChoice("Import Conflict", "There already is a process with the same ID and Name '$($destinationItem.Name)' in the repository.", $Options, $defaultChoice) switch($answer) { 0 { $ConflictAction = "CopySourceServerObject" } 1 { $ConflictAction = "UseDestinationServerObject" } } } switch ($ConflictAction) { "CopySourceServerObject" { # Copy object from source server to destination server $newProcess = $sourceItem | Copy-AMProcess -Folder $tempProcessFolder -Connection $Connection $substitions.Add($item.ConstructID, $newProcess.ID) } "UseDestinationServerObject" { # Use existing object in the destination server $substitions.Add($item.ConstructID, $destinationItem.ID) } } } else { # If the destination item does not already exist, create it $newProcess = $sourceItem | Copy-AMProcess -Folder $tempProcessFolder -Connection $Connection $substitions.Add($item.ConstructID, $newProcess.ID) } } else { # Enter a bogus GUID, will result in an unbuilt item $substitions.Add($item.ConstructID, (New-Guid).Guid) } } } } $newItem.ConstructID = $substitions[$item.ConstructID] } else { $newItem.ConstructID = $item.ConstructID } $newItem.ConstructType = $item.ConstructType } } # Retain the item ID if it does not already exist if ($copyToAnotherServer -and $item.ID -notin $existingIds) { $newItem.ID = $item.ID } $newItem.Enabled = $item.Enabled $newItem.Height = $item.Height $newItem.Label = $item.Label $newItem.UseLabel = $item.UseLabel $newItem.Width = $item.Width $newItem.WorkflowID = $copyObject.ID $newItem.X = $item.X $newItem.Y = $item.Y $copyObject.Items += $newItem $substitions.Add($item.ID, $newItem.ID) } foreach ($trigger in $currentObject.Triggers) { switch ($Connection.Version.Major) { 10 { $newTrigger = [AMWorkflowTriggerv10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newTrigger = [AMWorkflowTriggerv11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Schedules don't use an agent, so there's no reason to set it if ($trigger.TriggerType -ne "Schedule") { $newTrigger.AgentID = $trigger.AgentID } # If copying to another server if ($obj.ConnectionAlias -ne $Connection.Alias) { # Look for any specified substitions for the agent, use if provided if ($substitions.ContainsKey($trigger.AgentID)) { $newTrigger.AgentID = $substitions[$trigger.AgentID] } # If there is no substition specified for the item if (-not $substitions.ContainsKey($trigger.ConstructID)) { $idFilterSet = @{Property = "ID"; Operator = "="; Value = $trigger.ConstructID} $sourceItem = Get-AMCondition -FilterSet $idFilterSet -Connection $obj.ConnectionAlias # If the source item exists if ($null -ne $sourceItem) { if ($substitions.ContainsKey($sourceItem.ParentID)) { $tempConditionFolder = Get-AMFolder -ID $substitions[$sourceItem.ParentID] -Connection $Connection } else { $tempConditionFolder = $conditionFolder } $destinationItem = Get-AMCondition -FilterSet $idFilterSet,@{Property = "Name"; Operator = "="; Value = $sourceItem.Name} -Connection $Connection # If the destination item exists if ($null -ne $destinationItem) { # Prompt for conflict action if (-not $PSBoundParameters.ContainsKey("ConflictAction")) { $options = [System.Management.Automation.Host.ChoiceDescription[]] @("Copy &Source Server Object", "Use &Destination Server Object") $defaultChoice = 0 $answer = $host.UI.PromptForChoice("Import Conflict", "There already is a condition with the same ID and Name '$($destinationItem.Name)' in the repository.", $Options, $defaultChoice) switch($answer) { 0 { $ConflictAction = "CopySourceServerObject" } 1 { $ConflictAction = "UseDestinationServerObject" } } } switch ($ConflictAction) { "CopySourceServerObject" { # Copy object from source server to destination server $newCondition = $sourceItem | Copy-AMCondition -Folder $tempConditionFolder -Connection $Connection $substitions.Add($trigger.ConstructID, $newCondition.ID) } "UseDestinationServerObject" { # Use existing object in the destination server $substitions.Add($trigger.ConstructID, $destinationItem.ID) } } } else { # If the destination item does not already exist, create it $newCondition = $sourceItem | Copy-AMCondition -Folder $tempConditionFolder -Connection $Connection $substitions.Add($trigger.ConstructID, $newCondition.ID) } } else { # Enter a bogus GUID, will result in an unbuilt item $substitions.Add($trigger.ConstructID, (New-Guid).Guid) } } $newTrigger.ConstructID = $substitions[$trigger.ConstructID] } else { $newTrigger.ConstructID = $trigger.ConstructID } # Retain the item ID if it does not already exist if ($copyToAnotherServer -and $trigger.ID -notin $existingIds) { $newTrigger.ID = $trigger.ID } $newTrigger.TriggerType = $trigger.TriggerType $newTrigger.ConstructType = $trigger.ConstructType $newTrigger.Enabled = $trigger.Enabled $newTrigger.Height = $trigger.Height $newTrigger.Label = $trigger.Label $newTrigger.UseLabel = $trigger.UseLabel $newTrigger.Width = $trigger.Width $newTrigger.WorkflowID = $copyObject.ID $newTrigger.X = $trigger.X $newTrigger.Y = $trigger.Y $copyObject.Triggers += $newTrigger $substitions.Add($trigger.ID, $newTrigger.ID) } foreach ($link in $currentObject.Links) { switch ($Connection.Version.Major) { 10 { $newLink = [AMWorkflowLinkv10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newLink = [AMWorkflowLinkv11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Retain the link ID if it does not already exist if ($copyToAnotherServer -and $link.ID -notin $existingIds) { $newLink.ID = $link.ID } $newLink.DestinationID = $substitions[$link.DestinationID] $newLink.DestinationPoint.X = $link.DestinationPoint.X $newLink.DestinationPoint.Y = $link.DestinationPoint.Y $newLink.LinkType = $link.LinkType $newLink.ResultType = $link.ResultType $newLink.SourceID = $substitions[$link.SourceID] $newLink.SourcePoint.X = $link.SourcePoint.X $newLink.SourcePoint.Y = $link.SourcePoint.X $newLink.Value = $link.Value $newLink.WorkflowID = $copyObject.ID $copyObject.Links += $newLink } foreach ($variable in $currentObject.Variables) { switch ($Connection.Version.Major) { 10 { $newVariable = [AMWorkflowVariablev10]::new($Connection.Alias) } {$_ -in 11,22,23,24} { $newVariable = [AMWorkflowVariablev11]::new($Connection.Alias) } default { throw "Unsupported server major version: $_!" } } # Retain the variable ID if it does not already exist if ($copyToAnotherServer -and $variable.ID -notin $existingIds) { $newVariable.ID = $variable.ID } $newVariable.Name = $variable.Name $newVariable.ParentID = $copyObject.ID $newVariable.DataType = $variable.DataType $newVariable.Description = $variable.Description $newVariable.InitalValue = $variable.InitalValue $newVariable.Parameter = $variable.Parameter $newVariable.Private = $variable.Private $newVariable.VariableType = $variable.VariableType $copyObject.Variables += $newVariable } $copyObject | New-AMObject -Connection $Connection } else { Write-Error -Message "Unsupported input type '$($obj.Type)' encountered!" -TargetObject $obj } } } } |