PowerShell Function: Hashes Called by Reference? - function

I have several hashes and some functions should work with them. I want to make a call by reference for these hashes (do I need that):
$H1 = #{...}
$H2 = #{...}
$H3 = #{...}
...
function manage{ param( [ref]$a, [ref]$b, [ref]$c )
...
}
manage $H1 $H2 $H3
But this seems not to work. How do I do this correctly?
Thanks in advance

Hashes are considered reference types in PowerShell and are always being passed by reference so no need to use [ref] in the params here, see about_Ref.
Try without the [ref], or using [hashtable] type specifically:
function manage {
param (
[hashtable] $a,
[hashtable] $b,
[hashtable] $c
)
...
}
The changes you make to the hashes using the $a, $b, $c references inside the function will be done on the underlying hash objects and will be visible outside the function via the original $H1, $H2, $H3 references.

Related

Cannot use variable in a Where-Object in a function

I am trying to have a function that can count jobs based on the LatestStatus value that I would pass a parameter. So far what I got:
Function JobCountStatus {
Write-Output (Get-VBRJob | ?{$_.Info.LatestStatus -eq $args} | Measure-Object).Count
}
The issue is that as I've read somewhere there will be a subshell(?) executing the where so the argument is not passed.
If I replace the $args with a specific string like "Failed" it will work.
Is there a way to overcome this? I do not want to write separate functions for all possible values.
I would appreciate any comments - Thanks
Well you can just name the value when you run the function as $args is an Automatic Variable
JobCountStatus "Failed"
You can use an advanced function with a parameter, named or not:
function JobCountStatus {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[System.String]
$Status
)
Process {
(Get-VBRJob | Where-Object { $_.Info.LatestStatus -eq $Status } | Measure-Object).Count
}
}
And call it like so:
JobCountStatus -Status "Failed"
# OR
JobCountStatus "Failed"
The latter having the same end result as just using $args. The only possible advantage to specifying your own parameter here is you could define a ValidateSet of statuses or an Enum of Status values so you can tab through them. An example of the former would be like so:
function JobCountStatus {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[ValidateSet("Failed", "Running", "Successful", "Unknown")]
[System.String]
$Status
)
Process {
(Get-VBRJob | Where-Object { $_.Info.LatestStatus -eq $Status } | Measure-Object).Count
}
}
$args is an array, not a single value. In any case, a ScriptBlock {} is an unnamed function and $args has it's own meaning within it, so you can't use it without some modification in something like Where-Object. You would have to store the elements of $args as another variable or multiple variables to reference within a child ScriptBlock. Not a huge change for this function, but for one where more parameters are expected this can result in a lot of unnecessary code which can be difficult to maintain.
I prefer to recommend defining named parameters in most cases, and this would be a simpler change than making the parent $args work in a child ScriptBlock:
Function JobCountStatus {
Param(
[string]$Status
)
( Get-VBRJob | Where-Object { $_.Info.LatestStatus -eq $Status } ).Count
}
I've also made a few more changes in this function, I'll explain below:
Use Param() to strongly define parameters by name. You could use the simpler syntax of function JobCountStatus([string]$Status) {} but for this case it's really a matter of preference for which technique to use. Using Param() is something I recommend as a matter of convention, but you'll need to use either technique to get a named parameter.
I replaced the $args reference with $Status.
Your use of Measure-Object is extraneous and so I've removed it. Where-Object returns a collection which already has the Count property.
You can use ? if you want but it's considered best practice to omit aliases and use full cmdlet names in scripts and modules, so I've replaced ? with Where-Object.
Note that you can invoke the function/cmdlet (the difference is minimal for PowerShell-defined cmdlets) with or without the parameter, as when you don't define a positional order, the order is automatically determined in the order of declaration:
# Either will work
JobCountStatus -Status Running
JobCountStatus Running
Here is some more documentation you may find useful:
About Functions
About Advanced Functions
#Ash's answer gives some more advanced examples of what you can do with param() which are mentioned in the the Advanced Functions link above. You cannot use advanced parameter attributes with the simple function syntax I mentioned in the first bullet point.

How to check if a function parameter is a string or array in Perl

I'm trying to write a custom validateParameter function with Perl.
I have the following code which also works:
sub validateParameter {
my ($args, $list) = #_;
if ( ref($list) eq "ARRAY" ) {
foreach my $key (#$list) {
if ( not defined $args->{$key} ) {
die "no $key given!";
}
}
}
#elsif ( check if string ) {
#}
}
I want to call my function the following way:
validateParameter({ hallo => "Welt", test => "Blup"}, ["hallo", "test"]);
But I also want to call my function like this:
validateParameter({ hallo => "Welt", test => "Blup"}, "hallo");
I know that Perl only has the following three data-types (scalars, hashes, arrays). But maybe there is a smart way to check if a variable is a string.
How can I check if the given arg is a string?
Update: I somehow missed the end of the question. Just testing ref($list) eq 'ARRAY' will work most of the time, but to properly allow even overloaded objects, you should just try dereferencing the parameter:
if ( eval { \#$list } ) {
# it was an array
}
else {
# assume it is a string
}
Original answer:
You can check a number of things about a parameter:
if ( ! defined $param ) {
# undefined
}
elsif ( defined Scalar::Util::blessed($param) ) {
# object
}
elsif ( ref $param ) {
# reference (return of ref will give the type)
}
elsif ( length do { no warnings "numeric"; $param & '' } ) {
# number
}
else {
# string
}
But all of that (except perhaps the defined check) kind of defeats the purpose of Perl's automatically converting to your desired type and will limit what can be passed (for instance, a string or dualvar where a number is wanted, or an overloaded object where a string, number, or reference is wanted, or tied variables or magic variables such as $!).
You may want to also just look at what Params::Validate can do.
Don't base behaviour on the "type" of arguments because there really isn't such a thing. You will run into problems if you use type-base polymorphism because Perl values often have more than one type.
For example,
The scalar produced by "123" is stored as as string, but Perl doesn't distinguish it from the scalar produced by 123 which isn't stored as a string.
Scalars can contain both a number and a cached stringification of that number. (e.g. my $i = 123; "".$i;)
Scalars can contain both a number and a string (that isn't a stringification of the number). Common examples of these are $! and !1.
A reference to an object that overloads stringification is also a useful string. (e.g. DateTime->now)
A reference to an array may overload %{} to usable as a hash reference. A reference to an hash may overload #{} to usable as an array reference.
And more.
No, there is no way to check if a scalar is a string, as Perl does implicit type conversions depending on context. If you give a number as the second argument to your function, and you use it in a context that requires a string, it will be automatically converted to a string. So, just check if ref($list) is empty - in such case, $list is not a reference, and therefore it is a string or a number, and you don't need to distinguish between those two.

Repeatable Parameters in Powershell Function (Preferably Linked Parameter Sets)

I am wondering if it is possible (and if so how) to create repeatable (and hopefully linked) parameters in a PowerShell function. This is how am looking for this to work:
function foo()
{
[CmdletBinding()]
Params(
[Parameter(Mandatory=$true,ParameterSetName="Default")]
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
[string]$SomeParam1,
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
*some magic here, likely, to make this repeatable*
[string]$SomeRepeatableParam,
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[string]$SomeLinkedParam1,
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
[string]$SomeLinkedParam2
)
Begin
{
*some code here*
}
Process
{
foreach ($val in $SomeRepeateableParam)
{
*some code here using param and its linked param*
}
}
End
{
*some code here*
}
}
And then call this function like so:
foo -SomeParam "MyParam" -SomeRepeatableParam "MyProperty1" -SomeLinkedParam1 "Tall" -SomeRepeatableParam "MyProperty2" -SomeLinkedParam2 "Wide"
and so on, being able to use the repeatable parameter as many times in a single call as I feel like it.
Can this be done? And if so how?
Thanks for your time.
EDIT: For clarity, I don't mean an array parameter, but a repeatable parameter in which the linked parameter sets can be matched to each instance of the repeatable parameter.
Since PowerShell supports arrays as parameter values, there is generally no need to repeat a parameter.
There is no syntactic way to enforce the pairing (linking) of parameter values the way you intend, with repeating instances of the same parameter name, because parameter names must be unique (and even they didn't have to be unique, that alone wouldn't enforce the desired pairing).
You can, however, use parallel array parameters, and enforce their symmetry inside the function, e.g.:
function foo
{
[CmdletBinding()]
Param(
[string] $SomeParam1,
[string[]] $SomeRepeatableParam,
[string[]] $SomeLinkedParam
)
if ($SomeRepeatableParam.Count -ne $SomeLinkedParam.Count) {
Throw "Please specify paired values for -SomeRepeatableParam and -SomeLinkedParam"
}
for ($i = 0; $i -lt $SomeRepeatableParam.Count; ++$i) {
$SomeRepeatableParam[$i] + ': ' + $SomeLinkedParam[$i]
}
}
You would then call it as follows (note the , to separate the array elements):
foo -SomeParam1 "MyParam" `
-SomeRepeatableParam "MyProperty1", "MyProperty2" `
-SomeLinkedParam "Tall", "Wide"

Function parameter always empty why?

Can someone tell me, why this function call does not work and why the argument is always empty ?
function check([string]$input){
Write-Host $input #empty line
$count = $input.Length #always 0
$test = ([ADSI]::Exists('WinNT://./'+$input)) #exception (empty string)
return $test
}
check 'test'
Trying to get the info if an user or usergroup exists..
Best regards
$input is an automatic variable.
https://technet.microsoft.com/ru-ru/library/hh847768.aspx
$Input
Contains an enumerator that enumerates all input that is passed to a function. The $input variable is available only to functions and script blocks (which are unnamed functions). In the Process block of a function, the $input variable enumerates the object that is currently in the pipeline. When the Process block completes, there are no objects left in the pipeline, so the $input variable enumerates an empty collection. If the function does not have a Process block, then in the End block, the $input variable enumerates the collection of all input to the function.
Perhaps use a param block for parameters.
https://technet.microsoft.com/en-us/magazine/jj554301.aspx
Update: the problem seems to be fixed if you don't use $input as a parameter name, maybe not a bad thing to have proper variable names ;)
Also Powershell doesn't have return keyword, you just push the object as a statement by itself, this will be returned by function:
function Get-ADObjectExists
{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]
$ObjectName
)
#return result by just calling the object (no return statement in powershell)
([ADSI]::Exists('WinNT://./'+$ObjectName))
}
Get-ADObjectExists -ObjectName'test'

How do I package powershell advanced function args into a hashtable

I have a function
function Add-PromptSection() {
[CmdletBinding()]
Param(
[ConsoleColor]$Fore,
[ConsoleColor]$Back,
[Switch]$Newline,
[Scriptblock]$Condition,
[String]$String
)
$args = <something>
}
What I want to do is to collect the function arguments into a hashtable
#{ Fore=$Fore; Back=$Back; Newline=$Newline;
Condition=$Condition; String=$String }
However, if the user omitted a particular argument when calling the function, I want that key to be omitted from the hashtable. The idea is that after a bit of preprocessing, I will call Write-Host with the hashtable, as Write-Host #args. So I don't want something like $args["Back"] = $null in there.
Obviously I can do this by explicitly checking each argument (actually, can I? How do I tell if the user didn't supply $Fore? Checking for $null will work, I guess, but it's not quite the same behaviour as Write-Host) but is there a more compact approach?
It seems like what you're wanting (at least the hash table) is already being done for you by $PSBoundParameters.
if I understand your question, you are looking for default values for your arguments. Please correct me if I am wrong.
function Add-PromptSection() {
[CmdletBinding()]
Param(
[ConsoleColor]$Fore,
[ConsoleColor]$Back="Red",
[Switch]$Newline,
[Scriptblock]$Condition,
[String]$String
)
Write-Host $args
}
Now, the user can safely ignore providing $back in hashtable.