What is the logic behind flags and methods? - powershell

For example:
> "a,b,c" -split ","
a
b
c
> "a,b,c".split(",")
a
b
c
> "a,b,c".length
5
> "a,b,c" -length
At line:1 char:9
+ "a,b,c" -length
+ ~~~~~~~
Unexpected token '-length' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
So not every method can be expressed as an flag/argument list. I'm not even sure that .split and -split are the same thing or whether this is by chance.
When should I expect to use a flag and when a method? How do I discover all flags available (for a string, a number, etc.
Another thing is that ls -? returns a help text, but "foo" -? doesn't. So while it accepts flags, it is not really treated as a command

It boils down to this.
This ... "a,b,c" -length
About Operators
An operator is a language element that you can use in a command or expression.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-5.1
Because there are several operator types, the above is not a single source doc reference.
Vs this... "a,b,c".length
About methods
Describes how to use methods to perform actions on objects in PowerShell.
Methods allow you to examine, compare and format many properties of a PowerShell Object, perform an action.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-5.1
From your example :
String.Split Method
Returns a string array that contains the substrings in this instance that are delimited by elements of a specified string or Unicode character array
https://msdn.microsoft.com/en-us/library/system.string.split(v=vs.110).aspx
About Split
The Split operator splits one or more strings into substrings.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_split?view=powershell-5.1
String.Length Property
The Length property returns the number of Char objects in this instance, not the number of Unicode characters.
You'll note if you open this in the PowerShell_ISE.exe or VSCode, you'll see right away, the item 4 is immediately shown as an syntax error even before you run. This is denoted by the red squiggle. That marker means it will never work, so no real reason to try it.
Just because you can type it, does not make it right. If you type a '-' after a space for anything you get a list of what is expected. Well, if you are in the PowerShel_ISE or Visual Studio Code. If you are in the PowerShell console host, you have to hit the tab key to tab through the list or use CRTL + Spacebar to see the full list, then tab or arrow to what you want to use.
('a,b,c').length # this is an array, and this is returning the count of the elements in the array
5
('a,b,c','d,e,f').length # note the element count difference
2
('a,b,c').Length # property use of a .Net class
5
('a,b,c') -length # attempted unknown / invalid switch (PowerShell operator)
To know what you can and cannot do to an object or how, you have to what it supports. That is what Get-Member is for.
So the Array allows this..
('a,b,c') | Get-Member
and this..
('abc') | Get-Member
Most common listed
Name MemberType Definition
---- ---------- ----------
...
Split Method string[] Split(Params char[] separator), string[] Split(char[] separator, int count), string[] Sp...
...
Substring Method string Substring(int startIndex), string Substring(int startIndex, int length)
...
ToLower Method string ToLower(), string ToLower(cultureinfo culture)
...
ToString Method string ToString(), string ToString(System.IFormatProvider provider), string IConvertible.ToString...
...
ToUpper Method string ToUpper(), string ToUpper(cultureinfo culture)
...
Trim Method string Trim(Params char[] trimChars), string Trim()
TrimEnd Method string TrimEnd(Params char[] trimChars)
TrimStart Method string TrimStart(Params char[] trimChars)
Chars ParameterizedProperty char Chars(int index) {get;}
Length Property int Length {get;}
As for this...
When should I expect to use a flag and when a method?
How do I discover all flags available (for a string, a number, etc.
You have to read the help file on the cmdlet you are trying to use, or at minimum it's examples.
Get-Help -Name Get-ItemProperty -Full
Get-Help -Name Get-ItemProperty -Examples
Then info on the cmdlet / function you are trying to use
(Get-Command -Name Get-ItemProperty).Parameters
switches (flags) which will expect a value to the right of it or not, see the property values line below
Then what you can use the cmdlet/function on.
Get-ItemProperty -Path D:\Temp | Format-Table -AutoSize -Wrap
Get-ItemProperty -Path D:\Temp | Format-List
Get-ItemProperty -Path D:\Temp | Format-List -Force
Get-ItemProperty -Path D:\Temp | Select-Object -Property * # property values
(Get-ItemProperty -Path D:\Temp) | Get-Member
As for this...
Another thing is that ls -? returns a help text, but "foo" -? doesn't.
So while it accepts flags, it is not really treated as a command
Foo is not a valid name of anything in PowerShell, unless you created a foo function or module. So, it should hot return anything.
Again, just because you can type it, does not make it correct.
In most cases, if you are doing the above and you do not get automatic intellisense, then what you are doing is probably wrong.
To see all the cmdlets, functions, etc you have on your system for use. You do this.
Get-Command

Related

PS .Contains() method does not work on Get-Content return object

I tried to use the .Contains() method to check if a file contains a certain string:
# $file content: 123
$content = Get-Content -Path $file
$content.Contains("1") # Is always false, why?
"$content".Contains("1") # Works
Why are the " required so that the .Contains method works?
$content | Get-Member
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
(...)
Contains Method bool Contains(string value)
As commented, using Get-Content returns an array of lines.
By quoting that result "$content" PowerShell merges these single lines together into a single string and then the String's method .Contains() works as expected.
If you add switch -Raw to the Get-Content statement, the result is a single multiline string on which the .Contains() method works.
If you don't pass -raw parameter to Get-Content cmdlet, the content is returned as an array of newline-delimited strings.

What is the difference between char[] and char?

If I use the [char] datatype, it requires the value to be one character, e.x.
[char]"a"
a
Since if I use it with more than one character it will give me an error:
[char]"ab"
Cannot convert value "ab" to type "System.Char". Error: "String must be exactly one
character long."
At line:1 char:1
+ [char]"ab"
+ ~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastParseTargetInvocation
However if I use
[char[]]"ab"
I get the output
a
b
If I compare Get-Member on both, I get no result:
PS C:\Users\nijoh> Compare-Object -ReferenceObject $([char] | gm) -DifferenceObject $([char[]] | gm) -PassThru
PS C:\Users\nijoh>
But I can see that they are two distinct types since they show up differently:
PS C:\Users\nijoh> ([char[]]"a").GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Char[] System.Array
PS C:\Users\nijoh> ([char]"a").GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Char System.ValueType
So what is the difference between the [char] and [char[]] datatypes in Powershell?
In PowerShell, a type name enclosed in [...] is a type literal, i.e. an object that represents the .NET type named.
Independently of that, inside [...], [] immediately after a type name represents an array containing elements of that type; for an overview of the notation used inside [...], see this answer.
Therefore:
[char] refers to type System.Char (the System. prefix is generally optional in PowerShell; additionally - as is the case here - PowerShell has a fixed set of type accelerators that allow you to use simple names for types located in any namespace - see this answer).
To get a given type literal's full (namespace-qualified) .NET type name, use [...].FullName; e.g., [char].FullName yields System.Char
[char[]] refers to an array type whose elements are of type char ( System.Char); [char[]].FullName yields System.Char[].
Since if I use it with more than one character it will give me an error ([char]"ab")
PowerShell has no [char] literals, only string ([string]) literals. When you cast a string to [char] - which represents a single character - only a single-character string is accepted; e.g. [char] 'a' works, but [char] 'ab' or [char] "ab" doesn't.
However if I use [char[]]"ab" ...
Casting a string to an array of characters returns a character array comprised of the string's individual characters.
In other words: [char[]] "ab" is equivalent to "ab".ToCharArray().
If I compare Get-Member on both, I get no result:
The reason is that Get-Member operates on the types of the input objects, and if the input objects are type literals - such as [char] and [char[]] - their type is examined, which is System.RuntimeType, a non-public PowerShell type that derives from System.Reflection.TypeInfo, which describes a .NET type.
In other words: all type literals piped to Get-Member will result in the same output, irrespective of what specific type they refer to, because it is the one and only type-describing type whose members are being reported.
Therefore, using Compare-Object on Get-Member calls with different type literals predictably produces no output, because the Get-Member calls result in the same output[1]. (Not producing output is Compare-Object's way of indicating that no differences were detected.)
[1] Get-Member outputs an array of Microsoft.PowerShell.Commands.MemberDefinition instances, one for each member of the input object's type. In the absence of a -Property argument passed to Compare-Object, these instances are compared as a whole, by their .ToString() value, which yields a meaningful representation of each member.

Import-Csv data parse to array

I have a CSV file which contains multiline in some cells. I will use this data to compare the value got it from powershell.
This returns differences between the object, however the value is the same.
Expected Results should return nothing because both values are the same.
CSV content:
Value
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
Code:
PS> $data = Import-Csv .\tr.csv
PS> $data.Value
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
PS> $regval = ((Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedExactPaths).machine | Out-String).Trim()
PS> $regval
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
PS> Compare-Object $data.Value $regval
System\CurrentControlSet\Control\ProductOptions... =>
System\CurrentControlSet\Control\ProductOptions... <=
PS> $Tostring = ($data.Value | out-string).Trim()
PS> Compare-Object $Tostring $regval
InputObject SideIndicator
----------- -------------
System\CurrentControlSet\Control\ProductOptions... =>
System\CurrentControlSet\Control\ProductOptions... <=
PS> $Tostring.Length
145
PS> $regval.Length
147
This post no longer answers the OP's question directly but provides background information that is helpful for similar situations. This particular issue is solved by handling CR and LF characters before comparing the data. See Marked Answer for details.
Since $data in this case is an object with a property called value that holds your data, you need to compare what is stored in the value property to your $regval:
Compare-Object $data.value $regval
$regval is an array of strings before you pipe it to Out-String. After the pipe, it then becomes a string object. See below for type information before piping to Out-String.
$regval.gettype().fullname
System.String[]
$data is an array of objects (PSCustomObjects), which each have a property called Value that needs to be referenced directly if you want its data:
$data.gettype().fullname
System.Object[]
$data | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Value NoteProperty string Value=System\CurrentControlSet\Control\ProductOptions
($regval | Get-member).where({$_.MemberType -eq "Property"})
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
In order to compare the data of two objects using Compare-Object, best results seem to come when the objects are collections of the same type. PowerShell will automatically do conversions in the background in some cases like Compare-Object "1" 1. Maybe that has something to do with value types as I am not entirely sure. I would do the comparison before converting any of your data to different types. Then if you reference the Value property of $data, this condition becomes true:
$data.value | Get-member -type Property
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
You can reference MKlement0's Explanation for more information about how PowerShell handles the array type.
The likeliest explanation is that:
the multi-line value from your CSV (obtained from a single field) contains LF-only (Unix-style) newlines,
whereas the string derived form the registry values has CRLF (Windows-style) newlines, due to applying Out-String to an array of strings.
The most direct fix is to remove the CR chars. from $regval (you can use "`r" in PowerShell to generate a CR char):
# ...
# Remove all CRs from $regval.
# Note that not providing a replacement string (missing 2nd RHS operand)
# default to the empty string, which effectively *removes* what was matched.
$regval = $regval -replace "`r"
# Should now work as expected.
Compare-Object $data.Value $regval
That said:
Since you're comparing just two objects that are strings, you can avoid the overhead of Compare-Object and simply use -eq:
$data.Value -eq $regVal
Alternatively, you can split the multi-line values into arrays of lines and compare them individually; note that if you use regex "`r?`n" or ('\r?\n') to match newlines to split by - which matches both LF-only and CRLF newlines - you needn't remove CR chars. beforehand or even apply Out-String to the array output from the Get-ItemProperty HKLM:\... call to begin with; however, with the variable values from your question, you'd use:
# Newline-style-agnostic
Compare-Object ($data.Value -split "`r?`n") ($regval -split "`r?`n")
# Or, knowing that $data.Value has LF, and $regval CRLF
Compare-Object ($data.Value -split "`n") ($regval -split "`r`n")
# Or, by using the [string[]] array of registry values directly:
$regvals = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedExactPaths).machine
Compare-Object ($data.Value -split "`n") $regvals
As for what you tried:
$Tostring = ($data.Value | out-string).Trim()
If $data.Value is a single string that doesn't have a trailing newline - whether or not it has embedded newlines - the above is an effective no-op:
An input object that is already a string is passed through as-is by Out-String.
While Out-String does append a trailing CRLF newline (on Windows), the subsequent .Trim() call removes it again.

How do I access values in an ordered PowerShell hash table using integer keys?

My requirement is to store integer keys and access hash table values using those integer keys in an ordered hash table.
What works
When I use string keys, no problem:
cls
$foo=[ordered]#{}
$foo.add("12",1)
$foo.add("24",2)
write-host ("first item=" + $foo.Item("12"))
write-host ("second item=" + $foo.Item("24"))
Output:
first item=1
second item=2
Using Brackets Fails
When I use brackets, the program doesn't throw an exception, but it returns nothing:
$fooInt=[ordered]#{}
$fooInt.add(12,1)
$fooInt.add(24,2)
write-host ("first item=" + $fooInt[12])
write-host ("second item=" + $fooInt[24])
Output:
first item=
second item=
Using the Item method Fails
When I use the Item method and integer keys, PowerShell interprets the integer key as an index and not a key:
$fooInt=[ordered]#{}
$fooInt.add(12,1)
$fooInt.add(24,2)
write-host ("first item=" + $fooInt.Item(12))
write-host ("second item=" + $fooInt.Item(24))
Output:
Exception getting "Item": "Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index"
At line:8 char:1
+ write-host ("first item=" + $fooInt.Item(12))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], GetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenGetting
Exception getting "Item": "Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index"
At line:9 char:1
+ write-host ("second item=" + $fooInt.Item(24))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], GetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenGetting
How do I access values in a PowerShell hashtable using an integer key?
They keys in a hashtable are objects, not strings. When you're attempting to access the key "12" with the integer 12, it cannot find that entry because the keys don't match.
HOWEVER, you're not using a standard hashtable, you're using an ordered hashtable which has a different Item method on it since it can work by key or index. If you want to access the integer key with an ordered hashtable, you need to use a different syntax:
$hash.12
If you use the array accessor syntax:
$hash[12]
it will try to return the 13th item in the list.
You can observe the difference between these objects by using Get-Member:
$orderedHash | Get-Member Item
TypeName: System.Collections.Specialized.OrderedDictionary
Name MemberType Definition
---- ---------- ----------
Item ParameterizedProperty System.Object Item(int index) {get;set;}, System.Object Item(System.Object key) {get;set;}
$hash | Get-Member Item
TypeName: System.Collections.Hashtable
Name MemberType Definition
---- ---------- ----------
Item ParameterizedProperty System.Object Item(System.Object key) {get;set;}
After some more experimentation, this is only the case on the int32 type. If you define and access it with a different type, it will work since it's no longer matching the overloaded int signature:
$hash = [ordered]#{
([uint32]12) = 24
}
$hash[[uint32]12]
> 24
Summary
$fooInt.Item([object]12) or $fooInt[[object]12]
Reasoning
As seen in TheIncorrigible1's answer, .Item has overloads; it is backed by the method get_Item:
PS C:\> $fooInt.get_Item
OverloadDefinitions
-------------------
System.Object get_Item(int index)
System.Object get_Item(System.Object key)
System.Object IOrderedDictionary.get_Item(int index)
System.Object IDictionary.get_Item(System.Object key)
The version which takes an integer and does indexing comes from the IOrderedDictionary interface, the normal IDictionary key lookup takes a [System.Object]. When you try to use it with an integer argument, PowerShell binds the version which takes an [int] because it's a better match, and runs that one.
Earlier, I made a comment of how you could use reflection to pick out the overload you want, and invoke that, but it's ugly:
$fooInt.GetType().GetMethods().where{
$_.Name -eq 'get_Item' -and $_.GetParameters().Name -eq 'Key'
}.Invoke($fooInt, 'Public', $null, 12, $null)
^ your parameter
Thinking on it, [int] is a value type, not a reference type, and that means .Net has to box it into an object to put it in a Hashtable. So maybe if you also box your integer parameter into an object when doing a lookup, PowerShell might bind to the overload you want and do a key lookup and still match the correct key .. what do you know, it works:
PS C:\> $fooInt=[ordered]#{}
PS C:\> $fooInt.add(12,1)
PS C:\> $fooInt.add(24,2)
PS C:\> write-host ("first item=" + $fooInt.Item([object]12))
first item=1
And it works for indexing, too:
PS C:\> write-host ("first item=" + $fooInt[[object]12])
first item=1
Which is very close to TheIncorrigible1's experiment, except you don't need to define the dictionary with the key typed as something else and then cast your lookups to a matching type, you only need to access it with casting to object, because that's happening internally already for the keys you define.

Is a string parameter sent to PowerShell not really a string?

I am bit confused of the behaviour of the script below:
Test.ps1:
param(
[array]$Value = $(throw "Give me a value")
)
Write-Host $Value
$Value | Get-Member -MemberType Method
$Value.ToUpper()
Running the script:
PS C:\Temp> .\weird.ps1 test
TypeName: System.String
Name MemberType Definition
—- ———- ———-
…
ToUpper Method string ToUpper(), string ToUpper(System.Globalization.CultureInfo culture)
…
Method invocation failed because [System.Object[]] doesn’t contain a method named ‘ToUpper’.
At C:\Temp\weird.ps1:6 char:15
+ $Value.ToUpper <<<< ()
+ CategoryInfo : InvalidOperation: (ToUpper:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Why do I get a MethodNotFound exception? Get-Member clearly says it is a string.
What's happening here is that the variable $value is typed to Object[] in the script. The call to Get-Member works because you are piping the value into the function. Hence instead of seeing the array it sees the values in the array which are indeed typed to String. This can be viewed by using the following Get-Member call without piping
Get-Member -MemberType Method -InputObject $value
This is also why ToUpper correctly fails (it's an array not a String).
When you pipe a collection of anything in powershell, it gets "unrolled" and sent one by one to the right hand side of the bar (pipe) character. This means that the contents of the array get sent to get-member. Get-member only accepts the first item sent to it, so it shows you the members of the string. Your parameter is of type [array], so the parameter binder sets $value to an array of length 1, containing your string "test."
Example:
ps> #(1,"hello",3) | gm
... shows members of int32
ps> #("hello", 1, 3) | gm
... shows members of string
In order to see the members of an array when you pipe it, you should wrap it in another array, so it becomes the unrolled item:
ps> ,#("hello", 1, 3) | gm
... shows members of array
The leading comma "," creates a wrapping array.
-Oisin
$Value is actually an array, because that is how you declare the parameter in your param block.