How to output the referenced value? - powershell

I have the following function.
function Params {
param (
[Parameter(Mandatory = $true)]
[Alias('Param1')]
[AllowNull()]
${Param1[default value]}
)
[ref] $Param1 =
if (${Param1[default value]}) {
${Param1[default value]}
} else {
'default'
}
}
Params
$input1 = $null
"the param input is $([ref]$input1)"
If i input something for parameter on prompt or if i leave it to default value, i get this as output for $([ref]$input)
the param input is System.Management.Automation.PSReference`1[System.Management.Automation.LanguagePrimitives+Null]
Why am i not getting a value instead?
I want this output for example:
the param input is default

I ended up resorting to a different method to achieve what i want:
Defining this at the top of script:
[CmdletBinding()]
Param(
$Param1 = (Read-Host -prompt "Param1")
)
if (!$Param1) { "default" }
"the param input is $Param1"

The [ref] type accelerator (it's not a type accelerator in the usual sense, but it does create PSReference objects, so... it sort of is) gets you, as it tells you, a PSReference object.
In order to retrieve the value from it, you'd need to ask for it specifically. In your code, you can access it by pulling the Value property from the created reference object.
"the param input is $(([ref]$input1).Value)"
However, given that $input1 isn't assigned to, you might have to refactor a bit to get what you're after.

Related

Pass multiple parameters to function by pipeline

I'm having trouble passing two parameters via pipeline to a function.
function Test{
[cmdletbinding()]
param(
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=0)]
[string]$jeden,
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=1)]
[string]$dwa
)
Process{write-host "$jeden PLUS $dwa"}
}
"one", "two"|Test
What I expected as an outcome was
one PLUS two
but what I got was
one PLUS one
two PLUS two
I'm obviously doing something wrong, since both parameters get used twice. Please advise.
I got it to work by creating pscustomobject and piping it to function, where ValueFromPipelineByPropertyName property is set to true for both parameters.
function Test{
[cmdletbinding()]
param(
[parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true,Position=0)]
[string]$jeden,
[parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true,Position=1)]
[string]$dwa
)
Process{write-host "$jeden PLUS $dwa"}
}
$Params = [pscustomobject]#{
jeden = “Hello”
dwa = “There”
}
$Params |Test
OUTPUT:
Hello PLUS There
Variable assigning can be skipped and [pscustomobject] can be piped directly.
Have u tried to pass both strings as single object? Seems its ur pipeline is treating dem as 2 obj...
#("one", "two") | Test
EDIT. Try to define test in order to accept array:
function Test {
[cmdletbinding()]
param(
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=0)]
[string[]]$strings
)
Process {
write-host "$($strings[0]) PLUS $($strings[1])"
}
}

How can you mock an ErrorRecord with Pester in Powershell?

I have a function that I would like to include in my unit tests but I'm unable to figure out how. The method takes in an ErrorRecord object (from an Invoke-RestMethod call), grabs the status code and generates a custom error message. Here is what the first portion of the call looks like:
function Resolve-RestError {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
$RequestError
)
$statusCode = $requestError.Exception.Response.StatusCode.value__
switch ($statusCode) {
The problem that I am running into is in the recreation of the ErrorRecord object. I have spent many hours looking for a way to recreate the error object, but I am not able to recreate it exactly.
In the actual ErrorRecord object during execution, accessing the Response.StatusCode field returns a string with a description of the code. To access the actual value you need to call Response.StatusCode.value__. It seems that it's not possible to reconstruct an internal .value__ variable as this is done by the JIT compiler (from my understanding). Is there a way to either add a .value__ property to a custom object, or simply mock that variable during my tests?
You're right it doesn't seem overly simple to recreate an ErrorRecord object. My goto for this sort of thing is to simulate the output I want to Mock in my test and then store that result via Export-CliXml but when trying this with an ErrorRecord you only get as far as Exception.Response and the rest is lost in the conversion (even when setting a -Depth value).
There might be a smarter way, but one workaround would be to just fake the object as follows (note you didn't provide your full Function so I just created a simplified version of it to prove it worked for this specific case):
function Resolve-RestError {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
$RequestError
)
$statusCode = $RequestError.Exception.Response.StatusCode.value__
Return $statusCode
}
Describe 'Resolve-RestError' {
It 'Should handle a 404' {
$ErrorRecord = [pscustomobject]#{
Exception = #{
Response = #{
StatusCode = #{
value__ = 404
}
}
}
}
Resolve-RestError -RequestError $ErrorRecord | Should -Be 404
}
}

How to make a hashtable throw an error if key does not exists?

Using Powershell 5, I'd like to avoid an hashtable to return $null when a key is not present. Instead, I'd like to trow an exception.
To be clear :
$myht = #{}
$myht.Add("a", 1)
$myht.Add("b", 2)
$myht.Add("c", $null)
$myht["a"] # should return 1
$myht["b"] # should return 2
$myht["c"] # should return $null
$myht["d"] # should throw an exception
a, b, c are ok.
d isn't. It does not detect the missing key and return $null. I expect to throw a exception, because my business case allows $null, but not unknown values.
As a workaround, I try the .Net generic dictionary :
$myht = New-Object "System.Collections.Generic.Dictionary[string, System.Nullable[int]]"
It behaves, however, like the powershell hashtable.
At least, the only alternative I found is to wrap the test in a function:
function Get-DictionaryStrict{
param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[Hashtable]$Hashtable,
[Parameter(Mandatory=$true, Position=1)]
[string]$Key
)
if($Hashtable.ContainsKey($Key)) {
$Hashtable[$Key]
}
else{
throw "Missing value"
}
}
$myht = #{ a = 1; b = 2; c = $null }
Get-DictionaryStrict $myht a
Get-DictionaryStrict $myht b
Get-DictionaryStrict $myht c
Get-DictionaryStrict $myht d
It works the way I want, but the syntax is more verbose, especially when the call to the function takes place within other complex method.
Is there a simpler way ?
Use a Dictionary<TKey,TValue> type instead:
$dict = [System.Collections.Generic.Dictionary[string,object]]::new()
$dict.Add('a',$something)
$dict.Add('b',$null)
$dict.Item('a') # returns value of `$something`
$dict.Item('b') # returns `$null`
$dict.Item('c') # throws
You can use other collection types, but you could also use Strict Mode
Set-StrictMode -Version '2.0'
$x=#{a=5;b=10}
$x.a
$x.c
You get an error:
The property 'c' cannot be found on this object. Verify that the
property exists.
Just be careful not to break a working script as Sctrict Mode enforces a bunch of other stuff than error on non-existing property, like error on using non-existing variable or out of bound indexes. It depends on the level you use in Version.

powershell mandatory parameter with default value shown

I am looking for a way to have an PowerShell script ask for an parameter which needs to be mandatory, but shown with an default value, e.g.:
.\psscript
Supply values for the following parameters:
parameter1[default value]:
parameter2[1234]:
I want to ask for input but provide some default values.
If I use the mandatory option it asks for the values nicely but doesn't show the default value or process the given value. If I don't make it mandatory then PowerShell doesn't ask for the value at all.
Here's some script examples I tried:
[CmdletBinding()]
Param(
[parameter(Mandatory=$true)] $SqlServiceAccount = $env:computername + "_sa",
[parameter(Mandatory=$true)] $SqlServiceAccountPwd
)
This script asks for parameters but does not show or process the default value if I just press enter on the first parameter.
[CmdletBinding()]
Param(
[parameter(Mandatory=$false)] $SqlServiceAccount = $env:computername + "_sa",
[parameter(Mandatory=$true)] $SqlServiceAccountPwd
)
This script doesn't ask for the first parameter, but processes the default value.
Here's a short example that might help:
[CmdletBinding()]
Param(
$SqlServiceAccount = (Read-Host -prompt "SqlServiceAccount ($($env:computername + "_sa"))"),
$SqlServiceAccountPwd = (Read-Host -prompt "SqlServiceAccountPwd")
)
if (!$SqlServiceAccount) { $SqlServiceAccount = $env:Computername + "_sa" }
...
By definition: mandatory parameters don't have default values. Even if you provide one, PowerShell will prompt for value unless specified when the command is called. There is however a 'hacky' way to get what you ask for. As variables (and as consequence - parameters) can have any name you wish, it's enough to define command with parameters that match the prompt you would like to see:
function foo {
param (
[Parameter(Mandatory = $true)]
[Alias('Parameter1')]
[AllowNull()]
${Parameter1[default value]},
[Parameter(Mandatory = $true)]
[Alias('Parameter2')]
[AllowNull()]
${Parameter2[1234]}
)
$Parameter1 =
if (${Parameter1[default value]}) {
${Parameter1[default value]}
} else {
'default value'
}
$Parameter2 =
if (${Parameter2[1234]}) {
${Parameter2[1234]}
} else {
1234
}
[PSCustomObject]#{
Parameter1 = $Parameter1
Parameter2 = $Parameter2
}
}
When called w/o parameters, function will present user with prompt that match parameter names. When called with -Parameter1 notDefaultValue and/or with -Parameter2 7, aliases will kick in and assign passed value to the selected parameter. As variables named like that are no fun to work with - it makes sense to assign value (default or passed by the user) to variable that matches our alias/ fake parameter name.
I'd do it this way
param(
[Parameter(Mandatory)][string]$aString
)
if([string]::IsNullOrWhiteSpace($aString))
{
$aString = "A Default Value"
}
In my opinion, if you're using Read-Host in a param() block, then you're doing something wrong. At that point, what's the point of using param() at all?
There isn't a way to do what you want with a mandatory parameter and powershell prompting for you.
You would instead have to make it optional (remove mandatory), then implement the prompting code yourself (Read-Host, but take blank response as a default; something like that).

Settings default parameter values with XML values

I read somewhere that the param section must be the very first thing that appears in a script or function so this is what I have come up with in order to set the default values of each of the params. Yes, it is unorthodox, but it works.
Param (
[Xml]$xmlObj = (Get-Content "Download-VBK_config.xml"),
[String]$dlFrom = $xmlObj.Configuration.Download.From,
[String]$dlTo = $xmlObj.Configuration.Download.To,
[String]$exTo = $xmlObj.Configuration.Extract.To
)
However, is there a better way I could go about setting a param's default value by loading values from an XML file?
You can leave the parameters without default values, then look at the $PSBoundParameters variable to see what parameters were passed in, and fill the ones in that we're not passed.
Param(
[string]$Param1,
[string]$Param2)
[xml]$defaults =Get-Content file.xml
if(!$PSboundParameters.ContainsKey("Param1"))
{
$Param1 = $defaults.Configuration.Defaults.Param1
}