powershell: How to create and access static variables in a powershell class? - powershell

How to create a powershell class that has static variables?
class ex1 {
static [int]$count = 0
ex1() {
[ex1]::count = [ex1]::count + 1
write-host [ex1]::count
}
}
$ex1 = [ex1]::new()
$ex2 = [ex1]::new()
$ex3 = [ex1]::new()
exit 1
I tried this but all it does it prints:
[ex1]::new()
[ex1]::new()
[ex1]::new()
instead of incrementing the count to count the number of objects created in the static integer.

Apart from the constructor ex1() {..}, you need to add a method that actually returns the value of the static property Count:
class ex1 {
[int]static $count = 0
ex1() {
# constructor increments the static Count property
[ex1]::count++
}
[int]GetCount() {
# simply return the current value of Count
return [ex1]::count
}
}
$ex1 = [ex1]::new().GetCount()
$ex2 = [ex1]::new().GetCount()
$ex3 = [ex1]::new().GetCount()
$ex1, $ex2, $ex3 # --> resp. 1, 2, 3

Simple solution: Enclose static variable references like [ex1]::count in parentheses, whenever you want to pass them as an argument to another command like Write-Host:
Write-Host ([ex1]::count)
Why is that necessary when simple variables like $someVar don't require parentheses? For the same reason that Write-Host 2+2 doesn't print 4 but literally 2+2: PowerShells often counterintuitive argument parsing mode.
PowerShell switches from normal expression parsing mode to argument parsing mode, whenever it sees a call to a command (compare with a call to a .NET function, which doesn't change parsing mode). This mode follows its own rules, e. g. dollar sign followed by variable name causes PS to resolve the variable - as expected. Surprisingly there isn't a rule that covers static variable references, so PowerShell falls back to interpreting the argument as literal string.
Enclosing [ex1]::count in parentheses fixes the problem by forcing the parser to leave argument mode and parse an expression.
Full code sample with fix:
class ex1 {
static [int]$count = 0
ex1() {
[ex1]::count = [ex1]::count + 1
# Argument parsing mode -> wrap static variable reference in parentheses
write-host ([ex1]::count)
}
}
$ex1 = [ex1]::new()
$ex2 = [ex1]::new()
$ex3 = [ex1]::new()
[ex1]::count # Expression parsing mode -> no parentheses needed!
Note: The added last line is an example for PowerShells implicit output feature. By just naming a variable on its own line, PowerShell writes its value to standard output. As there is no command invocation involved, PowerShell is still in expression parsing mode, so we don't need to bracketize the expression.

Related

how to use hashtable to pass parameters to a powershell cmd that uses double dashes before the parameters

i m writing a script that makes use of a cmdlet from a console app, let's call the cmdlet as cmdA. I have no control over implementation of cmdA. cmdA takes parameters with a double dash (cmdA --param1 value1 --param2 value2 --param3 value3 --param4 value4 --param5 value5)
Now param2, param3, param4 and param5 are optional. The user of my script may or may not provide values for these optional parameters. In the script, i formed a hashtable using the parameters for which the user provided values. Let's call it paramHashtable.
I am executing cmdA #paramHashtable but this is not working. I think it has to do something with double dashes because this approach works fine with a cmd that takes parameters with single dash
What would be an efficient way to pass parameters to the cmdA (an inefficient way would be to use many if blocks to check which values were provided and make calls accordingly)
edited*
This is how i m creating the hashtable :-
$optionalParameters = #{}
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
$requiredParameters = #{"sqlConnectionStrings " = "$SqlConnectionStrings"}
$parameters = $requiredParameters + $optionalParameters
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
$parametersList = #{}
if($outputFolder -ne "")
{
$parametersList["outputFolder "] = $outputFolder
}
if($perfQueryIntervalInSec -ne "")
{
$parametersList["perfQueryIntervalInSec "] = $perfQueryIntervalInSec
}
if($staticQueryIntervalInSec -ne "")
{
$parametersList["staticQueryIntervalInSec "] = $staticQueryIntervalInSec
}
if($numberOfIterations -ne "")
{
$parametersList["numberOfIterations "] = $numberOfIterations
}
return $parametersList
}
This is how i m calling it :-
& $ExePath actionName #parameters
The $ExePath has the path of the program to be executed
The actionName takes parameters like this:-
actionName --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
Can splatting with hash table work on cmdlets / functions where it's parameter have dashes?
It may work, but it is definitely not a good idea to have parameter names with dashes as this will result in a function / cmdlet where named parameters cannot be used, PowerShell binds the arguments positionally! (thanks mklement0 for pointing this out):
function Test-Splatting {
param(${-param1}, ${-param2})
"${-param1} ${-param2}"
}
$param = #{ '--param1' = 'hello'; '--param2' = 'world' }
Test-Splatting #param # => hello world
Example of what was mentioned before using the same function above:
# --param1 is bound positionally and not interpreted as a parameter:
Test-Splatting --param1 hello # => --param1 hello
As for an external programs, the linked answer in comments explains very well the approach you could take using a wrapper function and the use of the automatic variable $args:
function myfunc {
$binaryPath = 'path/to/file.exe'
& $binaryPath actionName $args
# or #args we can't be sure until testing
}
myfunc --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
As noted in the comments, because you are calling an external program, you should use array-based splatting or simply arrays directly to pass programmatically constructed arguments.
By contrast, hashtable-based splatting is usually only helpful when calling PowerShell commands (while it technically works with external programs too, the resulting parameter format (e.g. -foo:bar or -foo:"bar none") is unusual and understood by few external programs)
Note that the parameter names and values must be passed as separate array elements and the elements representing parameter names must include the - or -- prefix; e.g., consecutive array elements
'--sqlConnectionStrings' (the name) and
'Data Source=Server1' (the value).
PowerShell then constructs a command line for invocation of the external program behind the scenes, space-separating the array elements and double-quoting elements with embedded spaces on demand; the above example turns into the following:
--sqlConnectionStrings "Data Source=Server1"
Note that it is up to the target program to parse the single string that is its command line in a way that recognizes parameter name-value pairs.
# Get the array of *optional* parameter names and values from your helper function.
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
# Declare the *required* parameter(s) as an array.
$requiredParameters = '--sqlConnectionStrings', $SqlConnectionStrings
# Pass the arrays as-is to the external program.
# (Empty arrays are effectively ignored.)
& $ExePath actionName $requiredParameters $optionalParmeters
Your ParameterSelection function can be simplified as follows:
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
# Loop over all bound parameters.
$PSBoundParameters.GetEnumerator() | ForEach-Object {
# Emit the parameter name prefixed with '--' and the value.
# PowerShell automatically collects the output strings in an array
# when you assign a call to this function to a variable.
'--' + $_.Key
$_.Value
}
}

Explicit Return in Powershell

I can write the following code in javascript:
function sum(num1, num2) {
return num1 + num2;
}
and then get a value
var someNum = sum(2,5);
I would like to do the same thing in Powershell, but I read the following guide:
PowerShell also knows the return keyword; however, it follows a
different logic. In general, the purpose of return is to end the
execution of a code section and to give the control back to the parent
block.
If you add a parameter to the return statement, the value will indeed
be returned to the calling subroutine. However, this also applies for
all other statements with an output. This means that any output
produced in the function will be stored in the variable together with
the return parameter.
I want to do this for the sake of having pure functions. However, it seems doing
var someNum = sum(2,5);
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
A bit tangential, but here is my actual code:
function GetPreviousKeyMD5Hashes() {
$query = "SELECT Name, MD5, executed FROM [AMagicDb].[dbo].cr_Scripts";
$command = New-Object System.Data.SQLClient.SQLCommand;
$command.Connection = $connection;
$command.CommandText = $query;
try {
$reader = $command.ExecuteReader();
while ($reader.Read()) {
$key = $reader.GetString(1)
$previousScripts.Add($key) | Out-Null
}
$reader.Close();
Write-Output "$(Get-Date) Finished querying previous scripts"
}
catch {
$exceptionMessage = $_.Exception.Message;
Write-Output "$(Get-Date) Error running SQL at with exception $exceptionMessage"
}
}
and then:
$previousScripts = New-Object Collections.Generic.HashSet[string];
GetPreviousKeyMD5Hashes;
This code isn't clear to me at all - running GetPreviousKeyMD5Hashes does set $previousScripts, but this is entirely unclear to whoever modifies this after me. My only other alternative (afaik) is to have all this in line, which also isn't readable.
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
No: functions execute in a child scope (unless you dot-source them with .), so variables created or assigned to inside a function are local to it.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
Yes: The implicit output behavior only applies to statements whose output is neither captured - $var = ... - nor redirected - ... > foo.txt
If there are statements that happen to produce output that you'd like to discard, use $null = ... or ... > $null
Note: ... | Out-Null works in principle too, but will generally perform worse, especially in earlier PowerShell versions - thanks, TheIncorrigible1.
If there are status messages that you'd like to write without their becoming part of the output, use Write-Host or, preferably Write-Verbose or, in PSv5+, Write-Information, though note that the latter two require opt-in for their output to be visible in the console.
Do NOT use Write-Output to write status messages, as it writes to the success output stream, whose purpose is to output data ("return values").
See this answer of mine for more information about PowerShell's output streams.
The equivalent of your JavaScript code is therefore:
function sum($num1, $num2) {
Write-Host "Adding $num1 and $num2..." # print status message to host (console)
$num1 + $num2 # perform the addition and implicitly output result
}
PS> $someNum = sum 1 2 # NOTE: arguments are whitespace-separated, without (...)
Adding 1 and 2... # Write-Host output was passed through to console
PS> $someNum # $someNum captured the success output stream of sum()
3
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
You can't have your cake and eat it too...
If you have no out put in your function, then it is "pure" like you desire. If you have output, that also becomes part of the return.
You can use [ref] params. See below for example.
function DoStuff([ref]$refObj)
{
Write-Output "DoStuff: Enter"
$refObj.Value += $(1 + 2)
$refObj.Value += "more strings"
Write-Output "DoStuff: Exit"
}
$refRet = #()
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
"`n`nagain"
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
Note: Powershell doesn't need semicolons at the end of each statement; only for separating multiple statements on the same line.
Whenever possible, it's a good idea to avoid changing global state within a function. Pass input as parameters, and return the output, so you aren't tied to using the function in only one way. Your sample could look like this:
function sum
{
param($num1,$num2)
return $num1+$num2
}
$somenum=sum 2 5
Now, with Powershell, the return statement isn't needed. The result of every statement that isn't otherwise assigned, captured, redirected, or otherwise used, is just thrown in with the return value. So we could replace the return statement above with simply
$num1+$num2
You're already making use of this in your code with:
$previousScripts.Add($key) | Out-Null
where you are discarding the result of .Add(). Otherwise it would be included in the return value.
Personally, I find using return to explicitly mark the return value makes it easier to read. Powershell's way of putting all if the output in the return caused a lot of trouble for me as I was learning.
So, the only fixes to your code I would make are:
Move $previousScripts = New-Object Collections.Generic.HashSet[string] to inside the function, making it local.
Add return $previousScripts to the end of the function.

Argument not passing in PowerShell [duplicate]

This question already has an answer here:
Pass object[] into a function in PowerShell
(1 answer)
Closed 4 years ago.
I have a PowerShell script where in the beginning of the code I created two simple functions to parse some strings:
Function createCase($subjid, $refids){
$ret = #{}
$ret.Add("subjid", $subjid)
$ret.Add("refids", $refids)
$ret.Add("subjents", #())
$ret.Add("refents", #())
return $ret
}
#Format input "subj;refid,refid,refid|subjid;refid,refid|subjid;refid"
function createCases($input){
$ret = #{}
$cases = $input.Split("|")
foreach ($case in $cases) {
$casearr = $case.Split(";")
$caseobj = createCase($casearr[0], $casearr[1].Split(","))
$ret.Add($casearr[0], $caseobj)
}
return $ret
}
Then a bit further I call upon this funcion createCases($input):
$input = "subj;refid,refid,refid|subjid;refid,refid|subjid;refid"
$cases = createCases($input)
Yet the $input variable in my previously defined function doesn't get filled and I end up with an error:
Method invocation failed because [System.Collections.ArrayList+ArrayListEnumeratorSimple] does not contain a method named 'Split'.
Which happens on the line:
$cases = $input.Split("|")
When I use the debugger the input argument is def set correctly to the string that I want it to be. Once we hop inside of the function it's gone.
It's due to the name "input" which is reserved in Powershell. Rename that variable and it should work better.
For more information I can refer you to this article:
https://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/

Powershell initializing global to $null is failing

I am running a GUI that takes user input but can be changed before finalizing if they make a mistake. When the form opens, I am trying to initialize the globals to null. This is failing. I put a breakpoint on the code and looked at the value before and then stepped into it. The value does not change.
So for example, if I run the form and enter "Foo" as my global variable, exit the form, then run the form again, even after the line in question executes, the value of the global is still "Foo". What is going on? I have used this exact code with other GUIs and it never failed (but the values were generated automatically rather than based on user input).
# Define and initialize global variables
$global:ServerName = $null # <-- This fails to reset the variable from the previous run of the form
function ValidateChoices(){
$OKToGo = $true
$TempServerName = $null
try {
# Only Allow Valid NETBios Name with AlphaNumberic and - up to 15 characters
[ValidatePattern("^[A-Za-z\d-]{1,15}$")]$TempServerName = $ServerNameTextbox.Text
$ServerNameTitle.BackColor = ""
$ServerNameTextbox.BackColor = ""
$global:ServerName = $TempServerName
} catch {
$OKToGo = $false
$ServerNameTitle.BackColor = "Pink"
$ServerNameTextbox.BackColor = "Pink"
}
...
if ( $OKToGo ){
"ServerName=" + $global:ServerName | Out-File c:\debug.txt
}
}
Here is the answer: When ValidatePattern is run against a variable, those restrictions are kept and re-evaluated anytime an attempt to change the variable is made. This holds true even if ValidatePattern was not explicitly called. And because it was a global variable, those restrictions rode through multiple iterations of the form. Because $null does not conform to my listed ValidatePattern parameters, the call to change the value was ignored

Passing a function to Powershell's (replace) function

I want to pass a function call(which returns a string) as a replacement string to Powershell's replace function such that each match found is replaced with a different string.
Something like -
$global_counter = 0
Function callback()
{
$global_counter += 1
return "string" + $global_counter
}
$mystring -replace "match", callback()
Python allows this through 're' module's 'sub' function which accepts a callback function as input. Looking for something similar
Perhaps you are looking for Regex.Replace Method (String, MatchEvaluator). In PowerShell a script block can be used as MatchEvaluator. Inside this script block $args[0] is the current match.
$global_counter = 0
$callback = {
$global_counter += 1
"string-$($args[0])-" + $global_counter
}
$re = [regex]"match"
$re.Replace('zzz match match xxx', $callback)
Output:
zzz string-match-1 string-match-2 xxx
PowerShell does not (yet?) have support for passing a script block to the -replace operator. The only option here is to use [Regex]::Replace directly:
[Regex]::Replace($mystring, 'match', {callback})