I'm got a problem parsing a file with key value pairs when one of the keys is named "length". Piping the content to ConvertFrom-StringData creates a hashtable with the key called "length" but, when I try to access it, I get the length of the table instead. Turns out this is because ConvertFrom-StringData is returning an array of hashtables and Length is the length of the array (6 in this case).
Any idea how to get around this? For a regular hashtable you can create a key called length and access it just fine ($tmp[1].length gives 1000um as it should). I won't usually know the index of the "length" field in the file, however.
> $tmp = Get-Content "Sample Settings.txt"
> $tmp
device=Hall bar
length=1000um
width=500um
thickness=8nm
system=PPMS
field=Perpendicular
> $tmp = $tmp | ConvertFrom-StringData
> $tmp
Name Value
---- -----
device Hall bar
length 1000um
width 500um
thickness 8nm
system PPMS
field Perpendicular
> $tmp.length
6
> $tmp[1].length
1000um
> $tmp.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
> $tmp[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
To get regular hashtable, use -Raw switch as follows:
$tmp = Get-Content "Sample Settings.txt" -Raw | ConvertFrom-StringData
Then:
$tmp
<#
Name Value
---- -----
device Hall bar
thickness 8nm
field Perpendicular
width 500um
system PPMS
length 1000um
<##>
$tmp.gettype().Name
# Hashtable
$tmp.Count
# 6
$tmp.length
# 1000um
Use IndexOf() to get the index and then use it to get the value:
$li = $tmp.keys.IndexOf('length')
$tmp[$li].length
Use -replace with a RegEx to prepend length with a string (here my):
> $tmp = $tmp -replace '^(?=length)','my' | convertfrom-stringdata
> $tmp
Name Value
---- -----
device Hall bar
mylength 1000um
width 500um
thickness 8nm
system PPMS
field Perpendicular
> $tmp.mylength
1000um
(?=..) is a zero length look ahead assertion
Related
I am using RestAPI access to azure. I am getting a list of changesets for a build ID.
I would like to sort the object type with increasing Changeset number.
The type I receive for $changeset.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
This is the output $changeset:
count value
----- -----
50 {#{id=68.......
Therefore, I checked the type of value $changeset.value.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
And afterwards I checked the type of an element:
$changeset.value[0].GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I did try Sort-Object with ascending and descending, but it does not change the order:
$changeset = $changeset | sort-object { $_.value.id } -desc
I would like to keep the structure as it is to not break something. There are lots of properties within the component.
Without an example working dataset, it's hard create an absolute working solution for your use case, however you could sort the data using the below:
$changeset | Select-Object -ExpandProperty value | Sort-Object -Property id
This would return all of the objects under the immediate property value and sorted on the property id.
If you want to retain the object as a whole with its present structure:
$changeset.value = #($changeset.value | Sort-Object -Descending -Property Id)
That is:
You must apply the Sort-Object call to the .value property, which contains the array of [pscustomobject] instances you want to sort by their .Id property values.
Enclosing the command in #(...), the array-subexpression operator, ensures that the sort objects are treated as an array, even if situationally only one object may be present.
Assigning the results back to $changeset.value replaces the original array with a new, sorted array.
I'm trying to save values between instances of the script running.
I'm doing that by reading a text file at the beginning of the script, and then overwriting that file with the new values at the end of the script.
tracker.txt:
x=1
y=4
z=3
I'm reading the script using:
Get-Content "$Root\tracker.txt" | Foreach-Object{
$Position = $_.Split("=")
New-Variable -Name $Position[0] -Value $Position[1]
}
Unfortunately my $x $y and $z variables are being interpreted as a string instead of an integer.
Looking up New-Variable parameters, it doesn't seem like I can specify the value type.
Also I tried:
New-Variable -Name $Position[0] -Value [int]$Position[1]
And:
New-Variable -Name $Position[0] -Value ($Position[1] + 0)
But both did not work as expected.
How can I set these variables as an integer? I'm trying to use them later in a loop and that keeps failing because the variable can't be a string.
In order to save values, consider using Powershell's own object serialization. That is, Export-Clixml and Import-Clixml cmdlets. When an object is serialized, its contents are written on a file. In addition to values, data types are there too.
Handling multiple variables is easier, if those are stored in a collection such as a hash table. Like so,
# Save some values in a hash table
$myKeys =#{ "a" = 1; "b" = 2 }
$myKeys["a"]
1
# Check variable a's type. Int32 is as expected
$myKeys["a"].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
# Serialize the hash table
Export-Clixml -Path keys.xml -InputObject $myKeys
# Create a new hash table by deserializing
$newKeys = Import-Clixml .\keys.xml
# Check contents
$newkeys["a"]
1
# Is the new a also an int32? Yes, it is
$newkeys["a"].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
The keys.xml is, as expected an XML file. Check out its contents with, say, Notepad to see how the objects are stored. When working with complex objects, -Depth switch needs to be used. Otherwise, serialization saves only two levels of nesting, and that'll break complex objects.
I will use this:
(Get-Content tracker.txt) | foreach {
invoke-expression "[int]`$$($_.split("=")[0])=$($_.split("=")[1])"
}
It will create three variables with type int32.
To answer your specific question, you can force it like this.
#'
var1=1
var2=2
'# | out-file "$Root\tracker.txt"
Get-Content "$Root\tracker.txt" | Foreach-Object{
$Position = $_.Split("=")
New-Variable -Name $Position[0] -Value ([int]$Position[1])
}
$var1.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
However I really like this approach.
Create a template
$template = #'
{name*:var1}={[int]value:1}
{name*:var2}={[int]value:2}
'#
Now use ConvertFrom-String and assign to $data
#'
test1=1
test2=2
test3=3
test4=4
test5=5
'# | ConvertFrom-String -TemplateContent $template -OutVariable data
Let's create our variables now. Very easy to read.
$data | foreach {New-Variable -Name $_.name -Value $_.value}
Get-Variable test*
Name Value
---- -----
test1 1
test2 2
test3 3
test4 4
test5 5
And most importantly, they are int.
$test1.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
Putting it all together and pulling from a file
$template = #'
{name*:var1}={[int]value:1}
{name*:var2}={[int]value:2}
'#
Get-Content "$Root\tracker.txt" -raw |
ConvertFrom-String -TemplateContent $template |
foreach {New-Variable -Name $_.name -Value $_.value}
What is the proper way to create an array, hashtable and dictionary?
$array = [System.Collections.ArrayList]#()
$array.GetType() returns ArrayList, OK.
$hashtable = [System.Collections.Hashtable]
$hashtable.GetType() returns RuntimeType, Not OK.
$dictionary = ?
How to create a dictionary using this .NET way?
What is the difference between dictionary and hashtable? I am not sure when I should use one of them.
The proper way (i.e. the PowerShell way) is:
Array:
> $a = #()
> $a.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Hashtable / Dictionary:
> $h = #{}
> $h.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
The above should suffice for most dictionary-like scenarios, but if you did explicitly want the type from Systems.Collections.Generic, you could initialise like:
> $d = New-Object 'system.collections.generic.dictionary[string,string]'
> $d.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Dictionary`2 System.Object
> $d["foo"] = "bar"
> $d | Format-Table -auto
Key Value
--- -----
foo bar
If you want to initialize an array you can use the following code:
$array = #() # empty array
$array2 = #('one', 'two', 'three') # array with 3 values
If you want to initialize hashtable use the following code:
$hashtable = #{} # empty hashtable
$hashtable2 = #{One='one'; Two='two';Three='three'} # hashtable with 3 values
Hashtable and dictionary in Powershell is pretty much the same, so I suggest using hashtable in almost all cases (unless you need to do something in .NET where Dictionary is required)
I am writing a powershell script to calculate summary stats for a csv file with 100,000+ rows.
In my foreach loop, one of my lines is:
$count = $Not_10000.count
Where "$Not_10000" is the result after filtering a csv, which was read using the import-csv command and filtered using
where {$_.ifhighspeed -eq 10000}.
I found that the value of "$count" is correct whenever "$Not_10000" has more than one line. However, when "$Not_10000" only has one line, the result is that $count is empty. I tried going into the powershell prompt and doing
$count = $Not_10000 | Measure-Object -lines
But it shows 0 lines even though it has one line. The output of
$Not_10000[0]
is
DATE ENTITYNAME IFHIGHSPEED
---- ---------- -----------
8/25/2014 12:00:00 AM SF15-0326 1000
Why wouldn't this one line output be counted correctly? I manually changed the filters to make "$Not_10000" contain 15 lines, and this was counted correctly.
I seem to have trouble in general giving the full picture, so let me know if you need more info or clarification.
Matt gives a good answer, but is only kind of the answer to your question.
The .Count property is a member of the Array object type that is being referenced when the number of results is more than one. When you only have one result then what is returned is not an array, it is a String, or an Object, or an Integer or something, but not an array unless you specifically make it one. In order for this to always come back with a number:
$count = $Not_10000.count
You need to cast it as an array, which is most simply done by enclosing it in #()
$count = #($Not_10000).count
This is most easily seen by using the .PSObject member of any object, when used on an array. Let's create an array, and look at the Type to verify that it's an array, and then look at the PSObject members (filtering for just properties, since it has a lot of members we don't care about) for that array object.
PS C:\Users> $test = #("Red","Blue","Green")
PS C:\Users> $test.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\Users> $test.PSObject.Members | Where{$_.MemberType -match "property"}|FT Name,MemberType,Value,ReferencedMemberName
Name MemberType Value ReferencedMemberName
---- ---------- ----- --------------------
Count AliasProperty 3 Length
Length Property 3
LongLength Property 3
Rank Property 1
SyncRoot Property {Red, Blue, Green}
IsReadOnly Property False
IsFixedSize Property True
IsSynchronized Property False
Item ParameterizedProperty ...int index) {get;set;}
What we see here is that it is an AliasProperty for the member Length, which on an array gives the number of records, just like it's alias Count does.
Use the Count property of Measure-Object. Lines, Words, Characters are things you use to measure text not objects
$count = $Not_10000 | Measure-Object | Select-Object -ExpandProperty Count
or
$count = ($Not_10000 | Measure-Object).Count
More Explanation
I have a csv with a header and 7 entries.
Path Data Files
---- ---- -----
\\someserver\somepath1 100 1
\\someserver\somepath2 150 4
\\someserver\somepath1 200 5
\\someserver\somepath3 450 8
\\someserver\somepath4 200 23
\\someserver\somepath1 350 2
\\someserver\somepath2 800 9
When i do the following (-lines is not a valid switch for Measure-Object )
Import-Csv E:\temp\stack.csv | Where-Object{$_.Data -gt 300} | Measure-Object -line
Since this is not text there are no lines to measure. I get this output regardless of how many entries in the file or filtered object. You would expect 3 but you actually get 0
Lines Words Characters Property
----- ----- ---------- --------
0
If i read the file as text i would get a result for lines
Get-Content E:\temp\stack.csv | Measure-Object -line
Lines
-----
8
I was thinking Unknown option is used for binary files concatenation.
http://technet.microsoft.com/en-us/library/dd315299.aspx
Unknown The encoding type is unknown
or invalid. The data can be treated as
binary.
But {Get-Content binary.dat -Encoding Unknown} doesn't return byte array but string array.
PS > $a = Get-Content $PSHOME\WTRInstaller.ico -Encoding Unknown
PS > $b = Get-Content $PSHOME\WTRInstaller.ico -Encoding Byte
PS > $a[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS > $b[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Byte System.ValueType
Even if I convert $a to byte array, it doesn't coincide with $b.
PS > [Text.Encoding]::Unicode.GetBytes($a)
PS > compare $c[0..10] $b[0..10]
InputObject SideIndicator
----------- -------------
10 =>
32 <=
When should 'Unknown' be used?
Uknown is not something that is supplied, but rather something that is returned. System.Text.Encoding is not just for use by powershell. There are many areas in the BCL that accept or return the same enums. Some values are for return, others for supplying.
-Oisin