Why weird assignment from variable inside Powershell switch statement? - powershell

I'm a beginner at Powershell and am struggling to understand some syntax from some code I found on Github. I've read the docs on Powershell assignment, and on switch statements, and can't understand what is going on with the = $Yes and = $No in this code snippet:
Switch ($Prompt3) {
Yes {
Stop-EdgePDF
Write-Output "Edge will no longer take over as the default PDF viewer."; = $Yes
}
No {
= $No
}
}
I haven't been able to find any references to this kind of syntax, and it doesn't seem to do anything in the script. So why is it there?

UPDATE: This issue has been resolved.
Looks to me like the variable name that was getting the assignment was deleted in a change back in August.
$PublishSettings = $Yes
Was changed to:
= $Yes
And:
$PublishSettings = $No
Was changed to:
= $No
Looks like poor search and replace.
I've created an issue for the problem at GitHub.

There are many characters that are valid in a function (or variable) name; this includes the = symbol. What you're observing is a function or alias.
Examples:
# standard function
function =
{
return $args
}
# accessing the function: drive
${Function:=} = {
return $args
}
# defining a new alias
New-Alias -Name = -Value Get-Variable
# using the Alias attribute
function Test-Thing
{
[Alias('=')]
param()
return $args
}

Related

Accessing array outside of a PS function

I am having a hard time figuring out how to get the PSCustomObject/array out of the function. I have tried using $Global:ZipList as well as just passing the variables into an array directly w/o a custom object but no luck. The reason I need this, is I need to then loop through the array/list after I get the filenames and then was going to loop through this list and unzip each file and log it and process it based on the extension in the zip; this is to be used for multiple zips, so I can't predetermine the file extensions without grabbing the filenames in the zip into a list. I would just use a shell however some of the zips are password protected, haven't figured out how to pass a password scripted to the shell com unzip windows feature so stuck with 7z for now. Any help would be greatly appreciated! Thanks
Function ReadZipFile([string]$ZipFileName)
{
[string[]]$ReadZipFile = & 'C:\Program Files\7-Zip\7z.exe' l "$ZipFileName"
[bool]$separatorFound = $false
#$ZipList = #()
$ReadZipFile | ForEach-Object{
if ($_.StartsWith("------------------- ----- ------------ ------------"))
{
if ($separatorFound)
{
BREAK # Second separator; We're done!
}
$separatorFound = -not $separatorFound
}
else
{
if ($separatorFound)
{
[DateTime]$FileCreatedDate = [DateTime]::ParseExact($_.Substring(0, 19),"yyyy'-'MM'-'dd HH':'mm':'ss", [CultureInfo]::InvariantCulture)
[Int]$FileSize = [Int]"0$($_.Substring(26, 12).Trim())"
$ZipFileName = $_.Substring(53).TrimEnd()
$ZipList = [PSCustomObject] #{
ZipFileName=$ZipFileName
FileCreatedDate=$FileCreatedDate
FileSize=$FileSize}
}
}
}
}
$z = ReadZipFile $ZipFileName
$ZipList | Select-Object ZipFileName
To be able to select from array created in the function outside of it. I believe my if statements may be blocking the global variable feature when i tried using global:

How to use a powershell function to return the expected value?

As we know, PowerShell has wacky return semantics.
Function return value in PowerShell shows there are two main ideas to wrap my head around:
All output is captured, and returned
The return keyword just indicates a logical exit point
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled. Another good one is $someCollection.Add("toto") which spits the new collection count. Even Append() function causes output.
For example :
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi");
$res2 = "toto"
return $res2
}
$s = MyFunc
Write-Host $s
The output is : titi toto.
The expected output should be toto.
How to use a powershell function to return the expected value? (at least when viewed from a more traditional programming perspective)
Change
$res1.Append("titi");
to
$res1.Append("titi") | Out-Null
because the function returns every output which otherwise would be visible in the console.
if by using 'toto' you are trying to understand if your function succeeded, you could do
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi") | Out-Null
return $?
}
"$?" returns a boolean if the previous command succeeded (true) or failed (false). so externally it would look like
$s = MyFunc
if ($s) {
Write-Host "successful" -Foregroundcolor Green
}
else {
Write-Error "unsuccessful"
}
When PowerShell was being developed, the team wanted to make it simple to use. But, it was confusing to people who know return from other languages. The implementation in classes is an attempt to rectify that mistake.
The return keyword works very differently in methods in PowerShell classes. It works like the return statements in other languages.
In a class method, the return keyword:
Exits the current scope.
Returns the associated object (return ).
Returns only the associated object.
The object that Return returns must match the return type of the method.
It is consistent with the return keyword and analogous keywords in other languages.
class ClassMyFunc
{
[string] MyFunc
{
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi")
$res2 = "toto"
return $res2
}
}
$cmf = New-Object -TypeName ClassMyFunc
$cmf.MyFunc()
The output is : toto, as expected.
Using classes solved my problem, without having to search all functions returning a value in the console and piping it to Out-Null (as suggested by #TobyU).

Powershell - persist variable outside of function

I'm trying to import an xml file and store as a variable for the remainder of a powershell session. The import is obviously successful but the variable content does not persist outside of the function.
Function auth
{
$cred = import-clixml -Path c:\temp\cred.xml
}
try this:
Function auth
{
$global:cred = "test"
}
auth
$global:cred
You can use globals as Esperento57 suggests or you can do this
function auth
{
return 'test'
}
$cred = auth
More succinct:
function auth
{
'test'
}
$cred = auth
You need to declare the variable outside the scope of the function first and then inside the function explicitly tell the variable to update using the script:var method.
Here's the example is taken from https://www.kongsli.net/2013/04/25/powershell-gotchas-refer-to-variables-outside-a-function/ to which credit is given.
The thing is that we have to explicitly tell Powershell to update the variable in the parent scope instead of creating a new variable in the current scope.
$x = 1
function changeit {
"Changing `$x. Was: $x"
$script:x = 2
"New value: $x"
}
"`$x has value $x"
changeit
"`$x has value $x"
If you need to do this but with a number of functions and variables, you can place them all into a script and then dotsource the script.
Imagine a script like this:
#MyDevFunctions.ps1
$myImportantVar = "somevar"
$myOtherVar = "ABC123"
Function Get-MyCoolValue(){$myImportantVar}
Write-Host "Finished Loading MyDevFunctions"
If you wanted to run this, and then also persist the values of the variables and also the functions themselves, from your parent script you simply invoke it like so:
PS > . .\MyDevFunctions.ps1
"Finished Loading MyDevFunctions"
PS > $myOtherVar
ABC123
PS> Get-MyCoolValue
someVar

How to check to see if what went through the hashtable matched anything

I have a hashtable and I'm trying to make an if statement right now that will check to see if what went through the hashtable matched anything within it.
$netVerConv = #{
'v2.0' = "lib\net20";
'v3.0' = "lib\net30";
'v3.5' = "lib\net35";
'v4.0' = "lib\net40";
'v4.5' = "lib\net45";
}
$target = $netVerConv.Get_Item($netVerShort)
if () {
}
Above is the area of my code I'm working with, the target variable runs $netVerShort through the $netVerConv hashtable using a Get_Item command. The if statement that I've laid the framework for would check to see if netVerShort matched anything within the hashtable and if it didn't it will stop the program, which I know how to do with a simple exit command.
The other suggestions will work in your specific scenario but in general you should use the ContainsKey() method to see if a key exists in the hashtable. For instance the hashtable value could be $null or $false in which case, testing via the result of Get_Item() or more simply Item[$netVerShort], will return a false negative. So I recommend this approach for testing existence of a key in a hashtable. It is also more obvious what your intent is:
if (!$netVerConv.ContainsKey($netVerShort) {
...
}
How about this:
if( $target -eq $null ) {
echo "Didn't Match"
exit
}
Another option:
if (-not ($target = $netVerConv.Get_Item($netVerShort)))
{
Write-Error "Version $netVerShort not found"
Exit
}
You could also re-factor that as a Switch
$target =
Switch ($netVerShort)
{
'v2.0' {"lib\net20"}
'v3.0' {"lib\net30"}
'v3.5' {"lib\net35"}
'v4.0' {"lib\net40"}
'v4.5' {"lib\net45"}
Default {
Write-Error "Version $netVerShort not found"
Exit
}
}

Powershell Error When Returning Collections.Generic.List[String]

I have the following code:
[Collections.Generic.List[String]]$script:Database = New-Object Collections.Generic.List[String]
[string]$script:DatabaseFile = "C:\NewFolder\database.csv"
function RunProgram
{
$script:Database = getCurrentDatabase #errors after this line
}
function getCurrentDatabase
{
[string[]]$database = Get-Content -Path $script:DatabaseFile
[Collections.Generic.List[String]]$databaseAsList = $database
return $databaseAsList
}
I get this exception after getCurrentDatabase returns:
Cannot convert the "system.object[]" value of type "system.object[]" to type "system.collections.generic.list`1[system.string]"
To get the code to work, I need to do this:
[Collections.Generic.List[String]]$script:Database = New-Object Collections.Generic.List[String]
[string]$script:DatabaseFile = "C:\NewFolder\database.csv"
function RunProgram
{
getCurrentDatabase #this works fine
}
function getCurrentDatabase
{
[string[]]$database = Get-Content -Path $script:DatabaseFile
$script:Database = $database
}
Why does the first way throw that exception, but the 2nd way doesn't?
EDIT:
I am using PS version 2.0 and C:\NewFolder\database.csv contains this one line:
Release Group,Email Address,Template ID,Date Viewed
That's something people trip on a lot...
When you output collections from function PowerShell will enumerate them, loosing original type as a part of the process. To prevent it from happening you can use unary comma:
function getCurrentDatabase
{
[string[]]$database = Get-Content -Path $script:DatabaseFile
[Collections.Generic.List[String]]$databaseAsList = $database
, $databaseAsList
}
Unary comma should prevent PowerShell from enumerating your collection (it actually enumerates collection of one that we've just created using unary comma).
I wrote a blog post about it a while ago - should help understand it in more detail.