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.
Related
I am using hashtables in powershell for multiple entries my hastable is working fine, but for the single entry its throwing the error by not picking the value it meant to be. Suppose I am having a Line
Aman;Indore;789858585;23
Raman;Delhi;785458545;35
for this two entry, my hashtable is working fine when i am giving command for example userinput.name[0] so it will pick Aman as the value and user.input.name[1] then it picks Raman as the value.
But the same code with single entry of
Aman;Indore;789858585;23
when I am giving userinput.name[0] so it is picking up A. The first letter of Aman it's picking instead of the complete name.
You might not even realise it, but you're using a PowerShell feature called Member Enumeration which was introduced in PowerShell 3.0.
Basically, if you attempt to access a named property on an array, if the property doesn't exist on the array object itself then PowerShell will look for that property on all the items in the array and return those values in a new array instead.
For example:
PS> $csv = #"
Aman;Indore;789858585;23
Raman;Delhi;785458545;35
"#
PS> $data = $csv | ConvertFrom-Csv -Delimiter ";" -Header #("Name", "Location", "Mobile", "Age");
PS> $data
Name Location Mobile Age
---- -------- ------ ---
Aman Indore 789858585 23
Raman Delhi 785458545 35
PS> $data.Name
Aman
Raman
# note this returns an array of objects thanks to member enumeration
PS> $data.Name.GetType().FullName
System.Object[]
PS> $data.Name[0]
Aman
In your case, $data.Name returns a new array containing the value of the Name property on all the items in $data - effectively #("Aman", "Raman"). So, when you use $data.Name[0], you're retrieving the first item in the array created by member enumeration - i.e. Aman - and all's well in the world...
Now, the catch is that if there's only one item in the array created by member enumeration it gets "unrolled" to be the value of the first item:
PS> $csv = #"
Aman;Indore;789858585;23
"#
PS> $data = $csv | ConvertFrom-Csv -Delimiter ";" -Header #("Name", "Location", "Mobile", "Age");
PS> $data
# Name Location Mobile Age
# ---- -------- ------ ---
# Aman Indore 789858585 23
# note this returns a single string because the "member enumeration" array
# gets unrolled if there's only one item
PS> $data.Name.GetType().FullName
System.String
PS> $data.Name
# Aman
PS> $data.Name[0]
# A
And in your case the $data.Name[0] is equivalent to "Aman"[0] which returns A.
To fix this, rather than inadvertently use member enumeration by doing $data.Name[0], where the result can vary based on the number of items in the array, you can use one of the following:
PS> $data[0].Name
Aman
PS> #($data.Name)[0]
Aman
The first option is probably more performant in the general case, but the second is a useful workaround in some cases where the first won't work (e.g. when dealing with return values from functions / where-object, etc).
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.
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
I've came across a PowerShell one liner script for which the very first character is a + (plus) sign and I was wondering what is the meaning of doing this.
Example that will give the Unicode code point for character 'A' :
+'A'['']
A unary + works as an implicit cast to the type int32.
The parser will simply try to convert the value on the right-hand side to an integer.
Let's look at (and step through) your statement, much like the parser would:
+'A'['']
Let's try to "tokenize" that statement:
+ 'A' [ '' ]
^ ^ ^ ^ ^
| | | | |
| | | | Array index close operator
| | | Empty string
| | Array index open operator
| Literal string of length 1 with value A
Unary + operator
In order to know whether we can apply the + operater, we'll need to evaluate the right-hand argument:
'A'['']
The only way we can index into a string (such as 'A'), is by treating it as a char[], and providing an integer value between the [ and ] operator. An empty string is not in itself an integer, but when implicitly converted to one, it becomes 0 (try [int]"" or '' -as [int] in powershell to see this in action). Now the statement looks more like this:
'A'[0]
This char at index 0 is obviously A, and so that is now our right-hand argument, the character uppercase A.
We now apply the unary + and voila, we get the corresponding ASCII value for the char A, which happens to be 65.
We could similarly have done:
+("A" -as [char])
Or, using Briantist's example:
"A" -as [char] -as [int]
If you ever wonder how the parser splits a certain statement into individual tokens, use the [PSParser]::Tokenize() method:
PS C:\> $errors = #()
PS C:\> $script = "+'A'['']"
PS C:\> $tokens = [System.Management.Automation.PSParser]::Tokenize($script,[ref]$errors)
PS C:\> $tokens | select Content, Type
Content Type
------- ----
+ Operator
A String
[ Operator
String
] Operator
It's used in code golfing to convert to a number. It's shorter than [int].
The significance of [''] is that the square brackets are being used to get a [char] from a string. The '' is an empty string being coerced into a 0.
The asker is referring to a solution to a specific problem, where one of the restrictions was that the digits 0 through 9 could not be used in the answer at all.
See the PowerShell One-Liner Contest 2015 and the explanation of this (rather brilliant) solution from the winner.
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.