When I add a help section to my script, the Get-Help cmdlet displays a different syntax. Here is a MWE:
#Require -Version 4.0
function global:Test-Syntax {
<#
.Synopsis
Cmdlet tests ValidateSet
#>
[CmdletBinding()]
# parameter check
param (
[ValidateSet("one", "two", "three")]
[string]$testparam
)
Write-Verbose "`$testparam: $testparam"
}
Get-Help Test-Syntax shows the following in syntax section:
SYNTAX
Test-Syntax [[-testparam] <String>] [<CommonParameters>]
After I remove .Synopsis or the whole help section, I receive the following from Get-Help:
SYNTAX
Test-Syntax [[-testparam] <string> {one | two | three}] [<CommonParameters>]
I would like to have the second one, because a user directly get the information about the validated set. How can I get this help with a defined .Synopsis?
This looks like a bug in how the syntax is automatically generated when you have comment help.
As a workaround, you could write a maml file and use the .ExternalHelp comment. This is an unfortunate workaround though, because maml is not easy to write, I would recommend using a tool, there is at least one commercial product or free ones on github/codeplex.
I would suggest opening an item here: https://windowsserver.uservoice.com/forums/301869-powershell
Related
Here is a simple test function called RegistryBoundParams.ps1:
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$Target,
[Parameter(Mandatory = $false)]
[switch]
$MySwitch
)
if(!(Test-IsAdmin)){
Request-AdminRights -NoExit
Exit
}
if($MySwitch){
"Do something" | Out-Host
}else {
"Do something else" | Out-Host
}
Show-AllArguments
If I call it via the PS terminal, everything works as expected:
Exact call: C:\Tools\scripts> .\RegistryBoundParams.ps1 -Target "C:\Test\" -MySwitch
If I call it through the registry (adding the command to a context menu), I get:
pwsh -noexit -file "C:\Tools\scripts\RegistryBoundParams.ps1" -Target "C:\Program Files\Python39\python.exe" -MySwitch
Plaintext of the error: RegistryBoundParams.ps1: A positional parameter cannot be found that accepts argument '$null'.
Here's a reg file that shows exactly what I added in the registry:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry]
#="Test Powershell Script from Registry"
"Icon"="C:\\Tools\\icons\\apps\\Powershell 1.ico,0"
"NeverDefault"=""
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry\command]
#="pwsh -noexit -file \"C:\\Tools\\scripts\\RegistryBoundParams.ps1\" -Target \"C:\\Program Files\\Python39\\python.exe\""
So somewhere along the lines $Null is being passed to the script, and I have no Idea why.
I could really, really use some help here.
Thanks so much for any guidance.
Edit:
I found that if I add a new string variable called $catchall, the script works. I suspect that when being called from the registry it's appending a null value for some reason. Which is why the script works when I define an additional "catch all" variable.
This is definitely not an ideal solution at all, so I am still looking for a solution here. Really appreciate any help!
Edit2:
It turns out that the Request-AdminRights script I was using that mklement0 authored had a bug that has now been fixed. Anyone who wants one-line self elevation with bound/unbound parameter support that's cross-platform... go get it!
The problem was a (since-fixed) bug in the code that you based your self-elevating function Request-AdminRights on:
The bug was that in the case of an advanced script such as yours, $args - which is never bound in advanced scripts - was mistakenly serialized as $null instead of translated to #(), resulting in that $null getting passed as an extra argument on re-invocation.
If you redefine your Request-AdminRights function based on the now updated body of the Ensure-Elevated function in the original answer, your problem should go away - no need to modify the enclosing script.
How do I customize the help command in JLine 3? The help in my JLine 3 shell sample displays as:
manager> help
System:
exit exit from app/script
help command help
Builtins:
ShellCommandRegistry:
create Create some stuff with minimal fuss...
delete Deletes some stuff with minimal fuss...
list List some stuff with minimal fuss...
I'd like to replace the section titles ("System:", "Builtins:", and "ShellCommandRegistry:") with single "Commands:" title like:
manager> help
Commands:
exit exit from app/script
help command help
create Create some stuff with minimal fuss...
delete Deletes some stuff with minimal fuss...
list List some stuff with minimal fuss...
Any ideas how to control this in JLine 3?
At the moment it is not possible to customize command groupings.
Will be fixed in next JLine version (> 3.15.0):
Added help command options: --nogroups (--groups) and --info.
Default grouping behaviour can be controlled by setting
systemRegistry.setGroupCommandsInHelp(true/false)
.
groovy-repl> help --help
help - command help
Usage: help [TOPIC...]
-? --help Displays command help
--nogroups Commands are not grouped by registeries
-i --info List commands with a short command infos
groovy-repl>
Using jline 3.20.0:
I wrote my own Registry and registered the other into this one, like this:
MyAppCommands myAppCommands = new MyAppCommands(parser, terminal, workDir, null);
myAppCommands.setCommandRegistries(builtins, picocliCommands);
myAppCommands.register("help", myAppCommands);
where MyAppCommands extends SystemRegistryImpl. Then the help command shows only the class-name "MyAppCommands:"
Therefore if you name your registry class "Commands" you'll get your desired result!
I want to write a unit-test for some code which generates a powershell script and then check that the script has valid syntax.
What's a good way to do this without actually executing the script?
A .NET code solution is ideal, but a command line solution that I could use by launching an external process would be good enough.
I stumbled onto Get-Command -syntax 'script.ps1' and found it concise and useful.
ETA from the comment below: This gives a detailed syntax error report, if any; otherwise it shows the calling syntax (parameter list) of the script.
You could run your code through the Parser and observe if it raises any errors:
# Empty collection for errors
$Errors = #()
# Define input script
$inputScript = 'Do-Something -Param 1,2,3,'
[void][System.Management.Automation.Language.Parser]::ParseInput($inputScript,[ref]$null,[ref]$Errors)
if($Errors.Count -gt 0){
Write-Warning 'Errors found'
}
This could easily be turned into a simple function:
function Test-Syntax
{
[CmdletBinding(DefaultParameterSetName='File')]
param(
[Parameter(Mandatory=$true, ParameterSetName='File', Position = 0)]
[string]$Path,
[Parameter(Mandatory=$true, ParameterSetName='String', Position = 0)]
[string]$Code
)
$Errors = #()
if($PSCmdlet.ParameterSetName -eq 'String'){
[void][System.Management.Automation.Language.Parser]::ParseInput($Code,[ref]$null,[ref]$Errors)
} else {
[void][System.Management.Automation.Language.Parser]::ParseFile($Path,[ref]$null,[ref]$Errors)
}
return [bool]($Errors.Count -lt 1)
}
Then use like:
if(Test-Syntax C:\path\to\script.ps1){
Write-Host 'Script looks good!'
}
PS Script Analyzer is a good place to start at static analysis of your code.
PSScriptAnalyzer provides script analysis and checks for potential
code defects in the scripts by applying a group of built-in or
customized rules on the scripts being analyzed.
It also integrates with Visual Studio Code.
There are a number of strategies for mocking PowerShell as part of unit tests, and also have a look at Pester.
The Scripting Guy's Unit Testing PowerShell Code With Pester
PowerShellMagazine's Get Started With Pester (PowerShell unit testing framework)
I'm relatively new to Chef/Ruby, and I'm trying clean up some old code from an old colleague. I have three recipes:
cookbook/providers/domain.rb
cookbook/providers/domaincontroller.rb
cookbook/providers/rename.rb
There's an identical ruby code block in each of these:
def computer_exists?
comp = Mixlib::ShellOut.new('powershell.exe -command \"get-wmiobject -class win32_computersystem -computername . | select domain\"').run_command
comp.stdout.include?(new_resource.name) || comp.stdout.include?(new_resource.name.upcase)
end
Is there a way I can wrap this block of code into a attribute, or something along those lines, so that we're not constantly re-writing the same 4 lines in each of the recipes?
Have a look at libaries.
You should be able to dump that exact code in a new file under libraries (e.g. cookbook/libraries/helper.rb) and then call computer_exists? from anywhere.
I'd like to have some usage statistics for a bunch of my modules.
It would be handy if I could run code whenever a function is called from a set of modules. Is it doable? Do powershell generate internal events we can hook on? I can not find any guidance yet
It's not completely clear to me whether you're more interested in logging events or executing code (hooking).
Logging
There are 2 places where in the event log where Powershell writes to the logs:
Applications and Services > Windows PowerShell
Applications and Services > Microsoft > Windows > PowerShell
On a per-module level, you can enable the LogPipelineExecutionDetails property. To do it on load:
$mod = Import-Module ActiveDirectory
$mod.LogPipelineExecutionDetails = $true
Or for an already loaded module:
$mod = Get-Module ActiveDirectory
$mod.LogPipelineExecutionDetails = $true
After that you check the first of the event log locations I listed (Windows PowerShell) and you'll see logs that show the calls to various cmdlets with the bound parameters.
You can also enable this via Group Policy as a Computer or User setting:
Administrative Templates > Windows Components > Windows PowerShell > Turn On Module Logging
You can specify the module(s) you want to enable logging for.
In PowerShell v5, there will be even more detailed logging available (see the link).
Source
You can see more detailed information about the logging settings (current and upcoming) on Boe Prox's blog: More New Stuff in PowerShell V5: Extra PowerShell Auditing
Hooking
As far as I know there is no direct way to hook calls in an existing module, but I have a crappy workaround.
You can effectively override existing cmdlets/functions by creating functions or aliases with the same name as the original.
Using this method, you could create wrappers around the specific functions you want to track. Consider something like this:
# Override Get-Process
function Track-GetProcess {
[CmdletBinding()]
param(
# All the parameters that the original function takes
)
# Run pre-execution hook here
& { "before" }
$params = #{}
foreach($h in $MyInvocation.MyCommand.Parameters.GetEnumerator()) {
try {
$key = $h.Key
$val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop
if (([String]::IsNullOrEmpty($val) -and (!$PSBoundParameters.ContainsKey($key)))) {
throw "A blank value that wasn't supplied by the user."
}
Write-Verbose "$key => '$val'"
$params[$key] = $val
} catch {}
}
Get-Process #params # call original with splatting
# run post execution hook here
& { "after" }
}
The middle there uses splatting to send the given parameters and sending them to the real cmdlet.
The hardest is part is manually recreating the parameter block. There are ways you could likely do that programmatically if you wanted to quickly run something to hook any function, but that's a bit beyond the scope of this answer. If you wanted to go that route, have a look at some of the code in this New-MofFile.ps1 function, which parses powershell code using powershell's own parser.