PowerShell and Control-M

PowerShell is the best scripting tool for various tasks of Microsoft products, Control-M is one popular automation tool in enterprise. But the current Control-M could not call PowerShell script directly.  I work in a customer project recently to migrate their legacy database maintenance jobs from dos batch to PowerShell, which scheduled by Control-M.  Here are my findings and sample code to address the challenges.

PowerShell version I use is PowerShell 5.0. You could use below PowerShell command to check the version in your environment.

[powershell] $PSVersionTable.PSVersion [/powershell]

Findings and Solutions

Finding How to address
Control-M could not call PowerShell directly. Cmd batch "wrapper" will be used to call PowerShell script.
Absolute path is needed in the cmd batch wrapper to call the PowerShell script. Put the cmd batch wrapper and PowerShell script in same folder, and use %~dp0 to set the working folder.
The PowerShell Try-Catch block could not capture all the errors in default setting. There are 2 types of errors in PowerShell, non-terminating errors and terminating errors. Only terminating errors will stop the execution and raise exception. And the Try-Catch block will then catch exception.Ref: https://technet.microsoft.com/en-us/magazine/2009.01.windowspowershell.aspx Set $ErrorActionPreference to change all non-terminating errors to terminating errors. Use Try-Catch block for the central error handling.
The way to pass back execution result to caller. For PowerShell, there are "return" and "exit" option. The "return" won't stop the execution of PowerShell, the "exit" will. The challenge is how PowerShell script call other PowerShell script or itself as recursion. "return" is used for function call, "exit" is used for wrapper call. Inside PowerShell script, the call to other PowerShell script is also through the wrapper.
How to combine with other enterprise monitoring tool like SCOM. Define your own error codes and write them to event log.
How to control the debug information output. Add a switch parameter, but it has to be int data type as switch type value($true or$false) is not supported by from cmd.

Sample Code

Batch Wrapper

[vb]
@echo off
setlocal
REM **********************************************************************************
REM * Name : control_m_demo.bat
REM * For : Wrapper of control_m_demo.ps1
REM * By : Shiyang Qiu (shiyang.qiu@microsoft.com)
REM **********************************************************************************

REM * There are 2 parameters, if any parameter is missing go to help
if "%1" == "" goto :HELP
if "%2" == "" goto :HELP

REM * Set the absolute path
SET SET_FOLDER=%~dp0
pushd "%SET_FOLDER=E%"

REM * control_m_demo.ps1 and control_m_demo.bat are in same folder
REM * %3 parameter is a hidden switch to control the output of debuggig info
powershell -file control_m_demo.ps1 %1 %2 %3

goto :END

:HELP

echo.
echo Command usage: use control_m_demo [para1] [para2] to check if para1/para2 < 1
echo.
echo [para1] = parameter 1
echo [para2] = parameter 2
echo.

:END

popd
endlocal

exit /b %errorlevel%

[/vb]

PowerShell Script

[ps]
############################################################
## File Name: control_m_demo.ps1
##
## Version 0.1
## Created Date 2 Aug, 2016
## Created By Shiyang Qiu (shiyang.qiu@microsoft.com)
############################################################

param
(
[Parameter(Mandatory=$true, Position=1)]
[int]$Para1,

[Parameter(Mandatory=$true, Position=2)]
[int]$Para2,

#Modify the switch to 0 when deploying the script. 1 is for dev and t-shooting
[Parameter(Mandatory=$false, Position=3)]
[ValidateSet(0,1)]
[int]$_Test = 1
)

## Change the default error action to "Stop"
$ErrorActionPreference = "stop"

## Change the working folder
Push-Location $PSScriptRoot

### Function PrintTestInfo is used to print debug info to screen
function PrintTestInfo($S)
{
write-host "$(get-date -Format yyyy-MM-dd_HH:mm:ss) Debug output: $S" -ForegroundColor Yellow
}

### Function IfDivResultLessThan1 is to calc para1/para2
function IfDivResultLessThan1
{
## when para2 is 0, it will trigger error
$Result = $Para1/$Para2

## return 1 when result < 1
## return 0 when result >=1
## error will be captured by outsdie try-catch block
if ($Result -lt 1) {return 1}
else {return 0}
}

########## Begin of Main Logic ######################

Try
{
if($_Test) {PrintTestInfo("begin the demo")}
if(IfDivResultLessThan1 -eq 1){PrintTestInfo("$para1/$para2 < 1")} else {PrintTestInfo("$para1/$para2 >= 1")}

## Exit 0 to wrapper when there is no error
if($_Test) {PrintTestInfo("without error")}
exit 0

}
catch
{

if($_Test)
{
$ErrorMsg = $Error[0].ToString()
$ErrorInvocationInfo = $Error[0].InvocationInfo.line

PrintTestInfo("Error info: $ErrorMsg.")
PrintTestInfo("Command: $ErrorInvocationInfo.")
}
## Exit 1 to wrapper when there is error
exit 1
}

########## End of Main Logic ######################
[/ps]

The result

result

HTH, Shiyang

Special thanks to David Ho for brain storming this solution.

---------
Aug 10 Update: https://blogs.msdn.microsoft.com/shiyangqiu/2016/08/10/errorlevel-and-lastexitcode/