Cannot extract PSVersion string from $PSVersionTable - string

I have the following output :
PS> $PSVersionTable
Name Value
---- -----
CLRVersion 2.0.50727.8806
BuildVersion 6.1.7601.17514
PSVersion 2.0
WSManStackVersion 2.0
PSCompatibleVersions {1.0, 2.0}
SerializationVersion 1.1.0.1
PSRemotingProtocolVersion 2.1
I'd like to get PSVersion value as a string like this into a variable : 2.0
So I tried this but it's not what I want :
PS> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
2 0 -1 -1
and these commands :
EDIT0 : I had copied'n'pasted the wrong line but still the string concatenation didn't work :
PS> $myVariable = $PSVersionTable.PSVersion.Major + "." + $PSVersionTable.PSVersion.Minor
Cannot convert value "." to type "System.Int32". Error: "Input string was not in a correct format."
At line:1 char:48
+ $myVariable = $PSVersionTable.PSVersion.Major + <<<< "." + $PSVersionTable.PSVersion.Minor
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
PS> $PSVersionTable | Select-Object PSVersion
PSVersion
---------
PS> $PSVersionTable | Get-Item PSVersion
Get-Item : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do
not match any of the parameters that take pipeline input.
At line:1 char:27
+ $PSVersionTable | Get-Item <<<< PSVersion
+ CategoryInfo : InvalidArgument: (System.Collections.Hashtable:Hashtable) [Get-Item], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.GetItemCommand
I'm a powershell rookie, can you help me ?

You can use the .ToString() method on the PSVersion key's value.
$MyVariable = $PSVersionTable.PSVersion.ToString()
Or:
$MyVariable = $PSVersionTable[ "PSVersion" ].ToString()
Note: The different syntax is because $PSVersion is a hash table or a dictionary object. PowerShell lets you use either syntax. Your executing the method on the value that's returned.
$MyVariable will be a string like "2.0". I double checked in version 5.1 and got "5.1.19041.546". If you want to isolate just the major version you can do it like:
$MyVariable = $PSVersionTable.PSVersion.Major
But this won't give you the minor point version.
You could also do something like:
$PSVersionTable.PSVersion.Major,$PSVersionTable.PSVersion.Minor -join "."
This will return "5.1" in my case. So, just major & minor versions but dropping build and revision information. It works by formatting the 2 values into an array and joining them into a string on a "."
Note regarding your question:
The first error is due to PowerShell's type conversion system. This is a little more complex to explain here, but PowerShell will attempt to convert data types to complete operations. It's intuitive but not perfect, and in this case it can't convert the "." to a number. One thing to point out is the tendency to convert the right side to the type of the left. Below would work.
$PSVersionTable.PSVersion.Major.ToString() + "." + $PSVersionTable.PSVersion.Minor
Above manually converts the left to a string so there is no trouble concatenating with ".", when the second "+" is evaluated the Right side is easily converted from [Int32] to [String], so it will work.
In this case I would prefer to use casting like:
[String]$PSVersionTable.PSVersion.Major + "." + $PSVersionTable.PSVersion.Minor
Effectively this is the same, but I prefer it from a syntax perspective.
The select command doesn't work because you are sending the hash table down the pipe and technically there is no "PSVersion" property to select. A correction to that might look something like:
$PSVersionTable.GetEnumerator() |
Where-Object{ $_.Name -eq "PSVersion" } |
Select-Object value
Obviously this is less convenient than other solutions, but I included it for illustration.
The third problem is just not a correct command or syntax. Get-Item cannot take the $PSVerrsionTable hashtable as input, piped or otherwise.

Related

String interpolation in PowerShell for hashmaps [duplicate]

I have the following code:
$DatabaseSettings = #();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;
When I try to use one of the properties in a string execute command:
& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
"RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"
It tries to just use the value of $DatabaseSettings rather than the value of $DatabaseSettings[0].DatabaseName, which is not valid.
My workaround is to have it copied into a new variable.
How can I access the object's property directly in a double-quoted string?
When you enclose a variable name in a double-quoted string it will be replaced by that variable's value:
$foo = 2
"$foo"
becomes
"2"
If you don't want that you have to use single quotes:
$foo = 2
'$foo'
However, if you want to access properties, or use indexes on variables in a double-quoted string, you have to enclose that subexpression in $():
$foo = 1,2,3
"$foo[1]" # yields "1 2 3[1]"
"$($foo[1])" # yields "2"
$bar = "abc"
"$bar.Length" # yields "abc.Length"
"$($bar.Length)" # yields "3"
PowerShell only expands variables in those cases, nothing more. To force evaluation of more complex expressions, including indexes, properties or even complete calculations, you have to enclose those in the subexpression operator $( ) which causes the expression inside to be evaluated and embedded in the string.
#Joey has the correct answer, but just to add a bit more as to why you need to force the evaluation with $():
Your example code contains an ambiguity that points to why the makers of PowerShell may have chosen to limit expansion to mere variable references and not support access to properties as well (as an aside: string expansion is done by calling the ToString() method on the object, which can explain some "odd" results).
Your example contained at the very end of the command line:
...\$LogFileName.ldf
If properties of objects were expanded by default, the above would resolve to
...\
since the object referenced by $LogFileName would not have a property called ldf, $null (or an empty string) would be substituted for the variable.
Documentation note: Get-Help about_Quoting_Rules covers string interpolation, but, as of PSv5, not in-depth.
To complement Joey's helpful answer with a pragmatic summary of PowerShell's string expansion (string interpolation in double-quoted strings ("...", a.k.a. expandable strings), including in double-quoted here-strings):
Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) can directly be embedded in a "..." string - that is, only the variable reference itself, as a whole is expanded, irrespective of what follows.
E.g., "$HOME.foo" expands to something like C:\Users\jdoe.foo, because the .foo part was interpreted literally - not as a property access.
To disambiguate a variable name from subsequent characters in the string, enclose it in { and }; e.g., ${foo}.
This is especially important if the variable name is followed by a :, as PowerShell would otherwise consider everything between the $ and the : a scope specifier, typically causing the interpolation to fail; e.g., "$HOME: where the heart is." breaks, but "${HOME}: where the heart is." works as intended.
(Alternatively, `-escape the :: "$HOME`: where the heart is.", but that only works if the character following the variable name wouldn't then accidentally form an escape sequence with a preceding `, such as `b - see the conceptual about_Special_Characters help topic).
To treat a $ or a " as a literal, prefix it with escape char. ` (a backtick); e.g.:
"`$HOME's value: $HOME"
For anything else, including using array subscripts and accessing an object variable's properties, you must enclose the expression in $(...), the subexpression operator (e.g., "PS version: $($PSVersionTable.PSVersion)" or "1st el.: $($someArray[0])")
Using $(...) even allows you to embed the output from entire commands in double-quoted strings (e.g., "Today is $((Get-Date).ToString('d')).").
Interpolation results don't necessarily look the same as the default output format (what you'd see if you printed the variable / subexpression directly to the console, for instance, which involves the default formatter; see Get-Help about_format.ps1xml):
Collections, including arrays, are converted to strings by placing a single space between the string representations of the elements (by default; a different separator can be specified by setting preference variable $OFS, though that is rarely seen in practice) E.g., "array: $(#(1, 2, 3))" yields array: 1 2 3
Instances of any other type (including elements of collections that aren't themselves collections) are stringified by either calling the IFormattable.ToString() method with the invariant culture, if the instance's type supports the IFormattable interface[1], or by calling .psobject.ToString(), which in most cases simply invokes the underlying .NET type's .ToString() method[2], which may or may not give a meaningful representation: unless a (non-primitive) type has specifically overridden the .ToString() method, all you'll get is the full type name (e.g., "hashtable: $(#{ key = 'value' })" yields hashtable: System.Collections.Hashtable).
To get the same output as in the console, use a subexpression in which you pipe to Out-String and apply .Trim() to remove any leading and trailing empty lines, if desired; e.g.,
"hashtable:`n$((#{ key = 'value' } | Out-String).Trim())" yields:
hashtable:
Name Value
---- -----
key value
[1] This perhaps surprising behavior means that, for types that support culture-sensitive representations, $obj.ToString() yields a current-culture-appropriate representation, whereas "$obj" (string interpolation) always results in a culture-invariant representation - see this answer.
[2] Notable overrides:
• The previously discussed stringification of collections (space-separated list of elements rather than something like System.Object[]).
• The hashtable-like representation of [pscustomobject] instances (explained here) rather than the empty string.
#Joey has a good answer. There is another way with a more .NET look with a String.Format equivalent, I prefer it when accessing properties on objects:
Things about a car:
$properties = #{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }
Create an object:
$car = New-Object -typename psobject -Property $properties
Interpolate a string:
"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package
Outputs:
# The red car is a nice sedan that is fully loaded
If you want to use properties within quotes follow as below. You have to use $ outside of the bracket to print property.
$($variable.property)
Example:
$uninstall= Get-WmiObject -ClassName Win32_Product |
Where-Object {$_.Name -like "Google Chrome"
Output:
IdentifyingNumber : {57CF5E58-9311-303D-9241-8CB73E340963}
Name : Google Chrome
Vendor : Google LLC
Version : 95.0.4638.54
Caption : Google Chrome
If you want only name property then do as below:
"$($uninstall.name) Found and triggered uninstall"
Output:
Google Chrome Found and triggered uninstall

Join three CSV columns and convert them to a formatted email link with Powershell [duplicate]

I have the following code:
$DatabaseSettings = #();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;
When I try to use one of the properties in a string execute command:
& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
"RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"
It tries to just use the value of $DatabaseSettings rather than the value of $DatabaseSettings[0].DatabaseName, which is not valid.
My workaround is to have it copied into a new variable.
How can I access the object's property directly in a double-quoted string?
When you enclose a variable name in a double-quoted string it will be replaced by that variable's value:
$foo = 2
"$foo"
becomes
"2"
If you don't want that you have to use single quotes:
$foo = 2
'$foo'
However, if you want to access properties, or use indexes on variables in a double-quoted string, you have to enclose that subexpression in $():
$foo = 1,2,3
"$foo[1]" # yields "1 2 3[1]"
"$($foo[1])" # yields "2"
$bar = "abc"
"$bar.Length" # yields "abc.Length"
"$($bar.Length)" # yields "3"
PowerShell only expands variables in those cases, nothing more. To force evaluation of more complex expressions, including indexes, properties or even complete calculations, you have to enclose those in the subexpression operator $( ) which causes the expression inside to be evaluated and embedded in the string.
#Joey has the correct answer, but just to add a bit more as to why you need to force the evaluation with $():
Your example code contains an ambiguity that points to why the makers of PowerShell may have chosen to limit expansion to mere variable references and not support access to properties as well (as an aside: string expansion is done by calling the ToString() method on the object, which can explain some "odd" results).
Your example contained at the very end of the command line:
...\$LogFileName.ldf
If properties of objects were expanded by default, the above would resolve to
...\
since the object referenced by $LogFileName would not have a property called ldf, $null (or an empty string) would be substituted for the variable.
Documentation note: Get-Help about_Quoting_Rules covers string interpolation, but, as of PSv5, not in-depth.
To complement Joey's helpful answer with a pragmatic summary of PowerShell's string expansion (string interpolation in double-quoted strings ("...", a.k.a. expandable strings), including in double-quoted here-strings):
Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) can directly be embedded in a "..." string - that is, only the variable reference itself, as a whole is expanded, irrespective of what follows.
E.g., "$HOME.foo" expands to something like C:\Users\jdoe.foo, because the .foo part was interpreted literally - not as a property access.
To disambiguate a variable name from subsequent characters in the string, enclose it in { and }; e.g., ${foo}.
This is especially important if the variable name is followed by a :, as PowerShell would otherwise consider everything between the $ and the : a scope specifier, typically causing the interpolation to fail; e.g., "$HOME: where the heart is." breaks, but "${HOME}: where the heart is." works as intended.
(Alternatively, `-escape the :: "$HOME`: where the heart is.", but that only works if the character following the variable name wouldn't then accidentally form an escape sequence with a preceding `, such as `b - see the conceptual about_Special_Characters help topic).
To treat a $ or a " as a literal, prefix it with escape char. ` (a backtick); e.g.:
"`$HOME's value: $HOME"
For anything else, including using array subscripts and accessing an object variable's properties, you must enclose the expression in $(...), the subexpression operator (e.g., "PS version: $($PSVersionTable.PSVersion)" or "1st el.: $($someArray[0])")
Using $(...) even allows you to embed the output from entire commands in double-quoted strings (e.g., "Today is $((Get-Date).ToString('d')).").
Interpolation results don't necessarily look the same as the default output format (what you'd see if you printed the variable / subexpression directly to the console, for instance, which involves the default formatter; see Get-Help about_format.ps1xml):
Collections, including arrays, are converted to strings by placing a single space between the string representations of the elements (by default; a different separator can be specified by setting preference variable $OFS, though that is rarely seen in practice) E.g., "array: $(#(1, 2, 3))" yields array: 1 2 3
Instances of any other type (including elements of collections that aren't themselves collections) are stringified by either calling the IFormattable.ToString() method with the invariant culture, if the instance's type supports the IFormattable interface[1], or by calling .psobject.ToString(), which in most cases simply invokes the underlying .NET type's .ToString() method[2], which may or may not give a meaningful representation: unless a (non-primitive) type has specifically overridden the .ToString() method, all you'll get is the full type name (e.g., "hashtable: $(#{ key = 'value' })" yields hashtable: System.Collections.Hashtable).
To get the same output as in the console, use a subexpression in which you pipe to Out-String and apply .Trim() to remove any leading and trailing empty lines, if desired; e.g.,
"hashtable:`n$((#{ key = 'value' } | Out-String).Trim())" yields:
hashtable:
Name Value
---- -----
key value
[1] This perhaps surprising behavior means that, for types that support culture-sensitive representations, $obj.ToString() yields a current-culture-appropriate representation, whereas "$obj" (string interpolation) always results in a culture-invariant representation - see this answer.
[2] Notable overrides:
• The previously discussed stringification of collections (space-separated list of elements rather than something like System.Object[]).
• The hashtable-like representation of [pscustomobject] instances (explained here) rather than the empty string.
#Joey has a good answer. There is another way with a more .NET look with a String.Format equivalent, I prefer it when accessing properties on objects:
Things about a car:
$properties = #{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }
Create an object:
$car = New-Object -typename psobject -Property $properties
Interpolate a string:
"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package
Outputs:
# The red car is a nice sedan that is fully loaded
If you want to use properties within quotes follow as below. You have to use $ outside of the bracket to print property.
$($variable.property)
Example:
$uninstall= Get-WmiObject -ClassName Win32_Product |
Where-Object {$_.Name -like "Google Chrome"
Output:
IdentifyingNumber : {57CF5E58-9311-303D-9241-8CB73E340963}
Name : Google Chrome
Vendor : Google LLC
Version : 95.0.4638.54
Caption : Google Chrome
If you want only name property then do as below:
"$($uninstall.name) Found and triggered uninstall"
Output:
Google Chrome Found and triggered uninstall

Powershell Base64 decoding - irregular behavior

I have a strange issue that I am finding when decoding base64 strings in Powershell.
$url = "https://*******.search.windows.net/indexes/azureblob-index/docs?api-version=2019-05-06&search=*"
$headers = #{
"api-version" = "2019-05-06"
"Content-Type" = "application/json"
"api-key" = "**********"
}
$result = Invoke-webrequest -Uri $url -Headers $headers -Method Get | ConvertFrom-Json
$values = $result.value
foreach ($value in $values)
{
$path = $value.metadata_storage_path
$bloburl = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($path))
$bloburl
}
The **** are hiding sensitive information, obviously.
So I am trying to return an Azure blob URL which is encoded. It managed to decode, however, it returns and error and seems to add a character to the end of the URL - making it out of sync with what the base64 decoding is expecting.
Result looks like this -
https://*******.blob.core.windows.net/files/REPORTS/*****/SEISMIC_ACQUISITION/ACQUISITION_REPORT_APPENDIX4_DAY_LOGS_JD_201.pdf5
It is always a number 5 that is added to the end of the string.
Any ideas as to what is going on here?
This is the full code - there is nothing else going on.
All that is happening is sending a search query to Azure search and returning the urls of blobs/documents which match the search query. Azure returns a base64 string and I want to decode that to plain readable text.
The error is:
Exception calling "FromBase64String" with "1" argument(s): "Invalid length for a Base-64 char array or string."
At line:25 char:9
+ $bloburl = [System.Text.Encoding]::UTF8.GetString([System.Con ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FormatException
It occurs at the line where it converts from base64.
The metadata_storage_path "base64 encoded" value is apparently a mangled version of base64 where any trailing "=" are removed, and a digit placed there to indicate how many "=" were removed. This is designed to allow the base64 string to be used a bit easier in urls.
See this question for more details:
How to decode metadata_storage_path produced by Azure Search indexer in .NET Core
You'll need to compensate for this modification to get back to a valid base64 encoded string before you can decode it. The linked answer gives some options for how to do this.

Advanced filter in PowerShell

I am trying to use the Excel advanced filter through PowerShell, but I am not having any luck. I can use an autofilter successfully by running the following code:
$rangetofilter = $worksheet2.usedrange.select
$excel.selection.autofilter(2, "TestFilter")
However, I don't understand how to properly convert the syntax given in Range.AdvancedFilter Method to something that PowerShell will accept. For example, I've tried
$excel.selection.AdvancedFilter("xlFilterInPlace", "", "", "TRUE")
But I get the following error:
Exception calling "AdvancedFilter" with "4" argument(s): "AdvancedFilter method of
Range class failed"
At line:1 char:32
+ $excel.selection.AdvancedFilter <<<< ("xlFilterInPlace","","","TRUE")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
So is there a way to run an Excel advanced filter through PowerShell?
I found this: Delete duplicate rows in excel using advanced filter, but it is not working either...
None of the arguments to AdvancedFilter() is a string.
Object AdvancedFilter(
XlFilterAction Action,
Object CriteriaRange,
Object CopyToRange,
Object Unique
)
The first is an enumeration. In VBA you can use those directly because there they are implicitly global. Not so in Powershell, where you have to reference them explicitly by their fully qualified names:
$xlFilterInPlace = [Microsoft.Office.Interop.Excel.XlFilterAction]::xlFilterInPlace
The other three arguments are typed as Object, which means they are of Variant type in COM. However, #2 and #3 are supposed to be Range objects, all bets are off if you pass in something else.
They are also marked as optional. Optional parameters that should have no value are represented by the Missing type in .NET COM Interop. Again, in Powershell you have to reference it explicitly:
$missing = [Type]::Missing
Argument #4 is supposed to be a Boolean, so just pass a Powershell bool constant (or, since this parameter is optional as well, $missing).
$excel.Selection.AdvancedFilter($xlFilterInPlace, $missing, $missing, $TRUE)

Place a Yes or No in an excel spreadsheet based on a value

Thanks in Advance!
Here is the code:I want to gather the value of the below variable...
$Sheet3.Cells.Item($intRowDisk, 4) = $objItem.Size/1024/1024/1024 - $objItem.FreeSpace/1024/1024/1024
Then I want to compare that value and if it is larger than 100 GB place a value of yes or no in a column within an excel spreadsheet.
if ( $Sheet3.Cells.Item($intRowDisk, 4) -gt 100 ) {
$Sheet1.Cells.Item($intRow, 12) = write "Yes"
But I get the error of : ERROR: Bad argument to operator '-gt': Could not compare "System.__ComObject" to "100". Error: "Cannot convert the "100" value of type "System.Int32" to type "System.
ERROR: __ComObject".".
IME_InventoryV1.8.ps1 (124): ERROR: At Line: 124 char: 49
ERROR: + if ( $Sheet3.Cells.Item($intRowDisk, 4) -gt <<<< 100 ) {
ERROR: + CategoryInfo : InvalidOperation: (:) [], RuntimeException
ERROR: + FullyQualifiedErrorId : BadOperatorArgument
Why do you have write "Yes" inside your if block? Does simply assigning "Yes" not work?
write is an alias for the Write-Object cmdlet, which puts content on the pipeline for the next command. It probably isn't what you want to use in this case. Try just assigning the string "Yes" and see if that solves your issue.
Also, on your comparison - while not particularly helpful, PowerShell is telling you that you can't compare a COM object and the value 100. You need to include a call to actually get the value out of the COM object for comparison by including the method invocation Value() at the end of your expression:
$Sheet3.Cells.Item($intRowDisk, 4).Value()
I figured it out. I had to add a variable and then feed the variable to the if statement. It now works like a charm.
$Sheet3.Cells.Item($intRowDisk, 4) = $objItem.Size/1024/1024/1024 - $objItem.FreeSpace/1024/1024/1024
$valueforif = $objItem.Size/1024/1024/1024 - $objItem.FreeSpace/1024/1024/1024
if ( $valueforif -gt 100 )
{
$Sheet1.Cells.Item($intRow, 12) = "Yes"
}

Resources