I have a simple code that accepts two parameters. The parameters are optional. Below is the code.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
when running the script I want to know which parameter is passed. pA or pB.
$MyInvocation.BoundParameters
return a ps custom dictionary pair (key/value) with all passed parameter.
this is the content of a.ps1 file:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
$MyInvocation.BoundParameters
running this script gives:
PS C:\ps> a -pA pAparam
Key Value
--- -----
pA pAparam
then you can check what key is present:
[bool]($MyInvocation.BoundParameters.Keys -match 'pa') # or -match 'pb' belong your needs
As you cas $Pa and $Pb you can test if they are empty:
You can test with this function :
function func
{
[CmdletBinding()]
Param([Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
if ($pA -eq [string]::Empty -and $pA -eq [string]::Empty)
{
Write-Host "Both are empty"
}
elseif ($pA -ne [string]::Empty)
{
Write-Host "Pa is not empty"
}
elseif ($pB -ne [string]::Empty)
{
Write-Host "Pb is not empty"
}
}
Clear-Host
func
Remains the problem that func -Pa "" will give same results as func But if you just want to test the presence of a parameter you can use the switch attribute.
You can find more about PowerShell scripts and function parameters with these links:
about_Functions
about_Functions_Advanced
about_Functions_Advanced_Parameters
Related
I'm trying to make a parameter mandatory, but only if another parameter uses certain ValidateSet values. It seems that using a code block on Mandatory doesn't work as expected.
function Test-Me {
[CmdletBinding()]
Param (
[Parameter()]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory = {-not ($Type -eq "NoNameRequired")})]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory? = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Set-StrictMode -Version Latest
function Test-Me {
[CmdletBinding(DefaultParameterSetName = "Gorgonzola")]
Param (
[Parameter(Mandatory)]
[int]
$Number,
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Number = '$Number'"
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Parameter sets seem to help simulate conditional mandatory parameters.
I can make it to where if either the Type or Name parameter is given, then they are both required. This can happen regardless of other parameters in the function, such as the sibling Number parameter above.
I set the default parameter set name to something random; I usually specify "None". That parameter set name doesn't need to actually exist, again indicated by the Number parameter.
All of this works regardless of your strict mode setting.
# cmdlet MyCmdlet.ps1
Param(
[Parameter(Mandatory=$true)]
[string]$Variable1,
[string]$Variable2
)
Begin {
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($Variable2)})]
[switch]$Choice
)
# function-body
}
}
Process {
Function-name -Variable1 "SomeString" -Choice
}
This cmdlet was called like below:
.\MyCmdlet.ps1 -variable1 "string1" -variable2 "string2"
It returns:
Cannot validate argument on parameter 'choice'. The "[string]::IsNullOrWhiteSpace($Variable2) " validation script for the argument with value "True" did not return a result of True.
It seems like the value of -Variable2 of cmdlet was implicitly passed to the function because of same variable name even without specically mentioning it during the function call inside the cmdlet.
Note: I need the variables to have same name so I can see their similar function in the future. And I use Begin, Process, End in cmdlet just so I can convert it into function and put in inside other scripts.
How can I deal with this?
By the time the ValidationScript runs, the local variable $Variable2 has not yet been created, so you get the value from $Variable2 in the parent scope.
Use $PSBoundParameters instead:
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($PSBoundParameters['Variable2'])})]
[switch]$Choice
)
}
If your intention is to create a script that could be both invoked directly (as a script) and dot-sourced (so you can call the function in it from other PowerShell scripts), you could do something like this:
# Invoke-Something.ps1
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #PSBoundParameters
} else {
Write-Host 'invocation: dot-source'
}
Using the above approach you can invoke the script directly:
PS> Invoke-Something.ps1 -Variable1 'some' -Variable2 'other'
invocation: directly
invocation: function
or dot-source it and invoke the function:
PS> . Invoke-Something.ps1
invocation: dot-source
PS> Invoke-Something -Variable1 'some' -Variable2 'other'
invocation: function
For this to work you must make all parameters of the script optional, though.
A simplified version of this would not define parameters on the script level and pass #args to the invoked function:
# Invoke-Something.ps1
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #args
} else {
Write-Host 'invocation: dot-source'
}
I have two powershell scripts, one with a function to check the value of a variable "name" which is passed as an argument. If it doesn’t match “John”, the result is 2 and else, the result is 0 (included within the function).
Here is the function script:
Function test {
Param (
$name = $(throw "need -name"),
$result = 0
)
Process {
if($name -ne "John") {
Write-Host "Name is incorrect"
$result = 2
} else {
Write-Host "Student is correct"
}
}
}
The second script calls this function and runs as -
Import-Module C:\function.ps1
test -name Sandra
Now I wanted to return the result (or a status) into the second script from the function, and add some more conditions into the second script based on the result. What are the changes required in the script or function to achieve this?
Seems like you are new to PowerShell, so here are some hints for you:
You should work with Approved Verbs e. g. change your function
name to Test-StudentName.
There is a language integrated support for parameter handling.
Using Write-Host is almost always wrong.
So you script could look something like this:
function Test-StudentName
{
[CmdletBinding()]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]
$Name
)
if ($name -ne 'John')
{
Write-Output 2
}
else
{
Write-Output 0
}
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Test-Name
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
# Parameter Name help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]
$Name
)
Begin
{
}
Process
{
if($name -ne "John") {
Write-Host "Name is incorrect"
2
}
else {
Write-Host "Student is correct"
0
}
}
End
{
}
}
and then you "dot source"
Windows PowerShell
Copyright (C) 2015 Microsoft Corporation. All rights reserved.
PS C:\Users\joshua> cd .\Desktop\
PS C:\Users\joshua\Desktop> . .\Test-Name.ps1
PS C:\Users\joshua\Desktop> Test-Name -Name Sandra
Name is inccorrect
2
The "dot sourcing" part is ". .\Test-Name.ps1". Or you can save the file as .psm1 -- that would be the most simplistic way to create a module -- and import it
PS
and yep, the usage of Write-Host is highly discouraged though not so much in powershell version 5
Is it possible in Powershell to dot-source or re-use somehow script functions without it being executed? I'm trying to reuse the functions of a script, without executing the script itself. I could factor out the functions into a functions only file but I'm trying to avoid doing that.
Example dot-sourced file:
function doA
{
Write-Host "DoAMethod"
}
Write-Host "reuseme.ps1 main."
Example consuming file:
. ".\reuseme.ps1"
Write-Host "consume.ps1 main."
doA
Execution results:
reuseme.ps1 main.
consume.ps1 main.
DoAMethod
Desired result:
consume.ps1 main.
DoAMethod
You have to execute the function definitions to make them available. There is no way around it.
You could try throwing the PowerShell parser at the file and only executing function definitions and nothing else, but I guess the far easier approach would be to structure your reusable portions as modules or simply as scripts that don't do anything besides declaring functions.
For the record, a rough test script that would do exactly that:
$file = 'foo.ps1'
$tokens = #()
$errors = #()
$result = [System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$tokens, [ref]$errors)
$tokens | %{$s=''; $braces = 0}{
if ($_.TokenFlags -eq 'Keyword' -and $_.Kind -eq 'Function') {
$inFunction = $true
}
if ($inFunction) { $s += $_.Text + ' ' }
if ($_.TokenFlags -eq 'ParseModeInvariant' -and $_.Kind -eq 'LCurly') {
$braces++
}
if ($_.TokenFlags -eq 'ParseModeInvariant' -and $_.Kind -eq 'RCurly') {
$braces--
if ($braces -eq 0) {
$inFunction = $false;
}
}
if (!$inFunction -and $s -ne '') {
$s
$s = ''
}
} | iex
You will have problems if functions declared in the script reference script parameters (as the parameter block of the script isn't included). And there are probably a whole host of other problems that can occur that I cannot think of right now. My best advice is still to distinguish between reusable library scripts and scripts intended to be invoked.
After your function, the line Write-Host "reuseme.ps1 main." is known as "procedure code" (i.e., it is not within the function). You can tell the script not to run this procedure code by wrapping it in an IF statement that evaluates $MyInvocation.InvocationName -ne "."
$MyInvocation.InvocationName looks at how the script was invoked and if you used the dot (.) to dot-source the script, it will ignore the procedure code. If you run/invoke the script without the dot (.) then it will execute the procedure code. Example below:
function doA
{
Write-Host "DoAMethod"
}
If ($MyInvocation.InvocationName -ne ".")
{
Write-Host "reuseme.ps1 main."
}
Thus, when you run the script normally, you will see the output. When you dot-source the script, you will NOT see the output; however, the function (but not the procedure code) will be added to the current scope.
The best way to re-use code is to put your functions in a PowerShell module. Simply create a file with all your functions and give it a .psm1 extension. You then import the module to make all your functions available. For example, reuseme.psm1:
function doA
{
Write-Host "DoAMethod"
}
Write-Host "reuseme.ps1 main."
Then, in whatever script you want to use your module of functions,
# If you're using PowerShell 2, you have to set $PSScriptRoot yourself:
# $PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
Import-Module -Name (Join-Path $PSScriptRoot reuseme.psm1 -Resolve)
doA
While looking a bit further for solutions for this issue, I came across a solution which is pretty much a followup to the hints in Aaron's answer. The intention is a bit different, but it can be used to achieve the same result.
This is what I found:
https://virtualengine.co.uk/2015/testing-private-functions-with-pester/
It needed it for some testing with Pester where I wanted to avoid changing the structure of the file before having written any tests for the logic.
It works quite well, and gives me the confidence to write some tests for the logic first, before refactoring the structure of the files so I no longer have to dot-source the functions.
Describe "SomeFunction" {
# Import the ‘SomeFunction’ function into the current scope
. (Get-FunctionDefinition –Path $scriptPath –Function SomeFunction)
It "executes the function without executing the script" {
SomeFunction | Should Be "fooBar"
}
}
And the code for Get-FunctionDefinition
#Requires -Version 3
<#
.SYNOPSIS
Retrieves a function's definition from a .ps1 file or ScriptBlock.
.DESCRIPTION
Returns a function's source definition as a Powershell ScriptBlock from an
external .ps1 file or existing ScriptBlock. This module is primarily
intended to be used to test private/nested/internal functions with Pester
by dot-sourcsing the internal function into Pester's scope.
.PARAMETER Function
The source function's name to return as a [ScriptBlock].
.PARAMETER Path
Path to a Powershell script file that contains the source function's
definition.
.PARAMETER LiteralPath
Literal path to a Powershell script file that contains the source
function's definition.
.PARAMETER ScriptBlock
A Powershell [ScriptBlock] that contains the function's definition.
.EXAMPLE
If the following functions are defined in a file named 'PrivateFunction.ps1'
function PublicFunction {
param ()
function PrivateFunction {
param ()
Write-Output 'InnerPrivate'
}
Write-Output (PrivateFunction)
}
The 'PrivateFunction' function can be tested with Pester by dot-sourcing
the required function in the either the 'Describe', 'Context' or 'It'
scopes.
Describe "PrivateFunction" {
It "tests private function" {
## Import the 'PrivateFunction' definition into the current scope.
. (Get-FunctionDefinition -Path "$here\$sut" -Function PrivateFunction)
PrivateFunction | Should BeExactly 'InnerPrivate'
}
}
.LINK
https://virtualengine.co.uk/2015/testing-private-functions-with-pester/
#>
function Get-FunctionDefinition {
[CmdletBinding(DefaultParameterSetName='Path')]
[OutputType([System.Management.Automation.ScriptBlock])]
param (
[Parameter(Position = 0,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName='Path')]
[ValidateNotNullOrEmpty()]
[Alias('PSPath','FullName')]
[System.String] $Path = (Get-Location -PSProvider FileSystem),
[Parameter(Position = 0,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = 'LiteralPath')]
[ValidateNotNullOrEmpty()]
[System.String] $LiteralPath,
[Parameter(Position = 0,
ValueFromPipeline = $true,
ParameterSetName = 'ScriptBlock')]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.ScriptBlock] $ScriptBlock,
[Parameter(Mandatory = $true,
Position =1,
ValueFromPipelineByPropertyName = $true)]
[Alias('Name')]
[System.String] $Function
)
begin {
if ($PSCmdlet.ParameterSetName -eq 'Path') {
$Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path);
}
elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
## Set $Path reference to the literal path(s)
$Path = $LiteralPath;
}
} # end begin
process {
$errors = #();
$tokens = #();
if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock') {
$ast = [System.Management.Automation.Language.Parser]::ParseInput($ScriptBlock.ToString(), [ref] $tokens, [ref] $errors);
}
else {
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors);
}
[System.Boolean] $isFunctionFound = $false;
$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true);
foreach ($f in $functions) {
if ($f.Name -eq $Function) {
Write-Output ([System.Management.Automation.ScriptBlock]::Create($f.Extent.Text));
$isFunctionFound = $true;
}
} # end foreach function
if (-not $isFunctionFound) {
if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock') {
$errorMessage = 'Function "{0}" not defined in script block.' -f $Function;
}
else {
$errorMessage = 'Function "{0}" not defined in "{1}".' -f $Function, $Path;
}
Write-Error -Message $errorMessage;
}
} # end process
} #end function Get-Function
I'm writing a powershell script which uses write-host to output another powershell script. How can I write out a boolean parameter with the value $true (or $false) including the dollar sign:
param
(
[switch] $myParam = $true
)
Write-Host My Param is $myParam
I need this to output exactly
My Param is $True
But it outputs
My Param is True
You can escape a $ with a `:
Write-Host My Param is `$$myParam
This would achieve what I think you want:
Write-Host My Param is `$$myParam