Mark Berry February 25, 2011
I’ve been working on some PowerShell scripts that I want to deploy to GFI MAX Remote Management, a monitoring tool I use in the MSP side of the business. I’m still working out the kinks, but here are some tips I’ll want to remember later. Maybe they’ll help you too.
Enable PowerShell Scripts on the Target Computers
Before anything will work, you’ll have to enable PowerShell on the target computers. At least on servers, it’s disabled by default. To enable, start PowerShell on the target machine and run set-executionpolicy. When prompted, tell it to change the policy to remotesigned. Update 3/2/2011: can also be set with Group Policy (see the bottom of this article; be aware of this possible issue).
The biggest challenge in deploying scripts is figuring out why they fail.
If you execute the script as an Automated Task and it fails, you may see the message “Invalid script arguments” in the GFI MAX dashboard. This forum post explains that the dashboard interprets a return code of 1 as “Invalid script arguments”. It seems that PowerShell returns 1 if the the script fails for any reason, so “Invalid script arguments” may or may not be the issue.
I discovered by trial and error that if I instead deploy the script as a 24×7 check and it fails, the failure message itself is returned in the dashboard and in the alert failure email. If you click on the dashboard message, you get the full message. Much more helpful.
Also, by deploying as a 24×7 check to a server on a 5-minute cycle, you’ll get to see output more frequently; in fact, you can open the Advanced Monitoring Agent on the server and execute (though not edit) the task:
Here is what I’ve been able to deduce regarding how MAX handles parameters.
1. If you pass parameters without any quotation marks, the parameters will be passed in to the script as is.
2. If you pass parameters surrounded by double quotation marks (“), they will be temporarily converted to single quotation marks so the script launcher can be called. I found this example of how the task is started by digging through MAX’s debug.log file:
cscript “C:\PROGRA~2\ADVANC~1\task_start.js” 156 “powershell.exe -NoLogo -NoProfile -NonInteractive .\1090.ps1 ‘My param'”
Inside task_start.js, we have this line which converts the single quotation marks back to double before passing them to the script:
// Re-insert the double quote character which cannot be passed through command line
sCommand = sCommand.replace(/\'/g,'\"');
(The /g tells it to do a global replace. See this article.)
3. If you pass parameters surrounded by single quotation marks (‘), the parameter string is passed to task_start.js as is. However, task_start.js will again convert those single quotation marks to double, so they will be double when calling PowerShell.
The PowerShell Problem
The problem is, PowerShell doesn’t like double quotation marks around parameters.
I have a little PowerShell script that echoes back one parameter:
[Parameter(Mandatory = $true,
Position = 0,
ValueFromPipelineByPropertyName = $true)]
Write-Host ('Test script output. TestParam = "' + $TestParam + '"')
If I call it with double quotation marks around the parameter, it thinks it’s two parameters, and the script fails since it only accepts one parameter:
If I call it with single quotation marks around the parameter, the script succeeds:
However, MAX never calls a script with single quotation marks, so PowerShell parameters with quotation marks will never work.
Two Workarounds and a Proposal
The first workaround is to avoid parameters containing spaces; then you can pass parameters without any quotation marks.
The second workaround is to use a special character to represent a single quotation mark, then to modify task_start.js on each target machine to convert that character back to the single quotation mark. As a test, I specified the back-quote (`) around my parameter, then added this line to task_start.js:
sCommand = sCommand.replace(/`/g,"'");
This isn’t very safe, or practical, since GFI can replace the task_start.js script at any time.
The proposal is that the GFI engine pass single quotation marks as special strings, perhaps the XML entity
"). That should allow both single and double quotation marks to be preserved in the parameter string. This becomes especially important when you want to pass an array as a parameter to PowerShell, e.g. @(“First element”,”Second element”).
The next problem is, even if we get the parameter quoted correctly, task_start.js is hard coded to append an extra parameter to the command before executing it:
oExec = oShell.Exec( sCommand + " -logfile ..\\task_" + nUID + ".log" );
nUID is an internal task ID used by MAX, so for task 156, it would append
" –logfile .\\task_156.log". Apparently it wants to save a log file to the Advanced Monitoring Agent folder (one level up from the Scripts folder). But I haven’t allowed for a –logfile parameter in my PowerShell script, so it ends with this error: “A parameter cannot be found that matches parameter name ‘logfile’.”
Note that PowerShell doesn’t complain about the extra –logfile parameter if the script does not require parameters. But a flexible script probably will require parameters, so we really need to get MAX working with parameters.
If I modify the task_start.js command to omit the –logfile parameter:
oExec = oShell.Exec( sCommand );
it does in fact complete successfully and comes back to the dashboard with the script output:
But I’m nervous about removing the –logfile parameter from the task_start.js script. It might be there for a reason ;).
I’ll see if I can get some clarification on the way GFI MAX handles PowerShell scripts with parameters. Stay tuned for updates.
Update March 2, 2011
GFI Support has provided the following suggestions:
- To enclose a parameter containing spaces, use three double quotation marks on each side of the parameter, e.g.
"""my param""". I haven’t been able to find this documented, but PowerShell apparently accepts three double quotation marks as one single quotation mark.
- Yes, GFI MAX always passes two additional strings to the script, –logfile and the path to a log file. GFI suggests writing scripts to accommodate these strings. PowerShell interprets that as one parameter named “LogFile”. If you actually write to the path specified by -logfile, it should be available in the Advanced Monitoring Agent folder, but according to GFI Support, “We tend to use this file for debugging purposes – you are able to write to this yourself, but we do not recommend that you use this file for long term storage of logs.”
Bonus – Script Output
GFI Support has also confirmed that you can output up to 1 MB of plain text from your script and see that output directly in the dashboard. The first line is displayed in the grid view; if you click on that hyperlink, a More Information dialog opens that shows the rest of the output. That’s very cool, and should eliminate the need to send script results by email in most cases.
In fact, if you return a non-zero (“failure”) exit code and deploy the script as a 24×7 check, it will even send you an email containing all the results. The same should work for a daily DSC check (not tested). It does not currently work for an Automated Task, since MAX only sees exit code 1, which it reports as “Invalid Script Arguments.” (MAX reserves return codes 1000 and below: see this post.)
Even if you return 1001 from PowerShell, it doesn’t get back to MAX. This has to do with how PowerShell returns exit codes as explained in this article. Updating task_start.js to append “;exit $LASTEXITCODE” to the command line when calling PowerShell should allow MAX to “see” the actual PowerShell return code.