I'm trying to use PowerShell Invoke-RestMethod on a ticketing system's API and then convert the output into a PowerShell object.
As an example when I use Invoke-RestMethod to get the properties of a ticket I get this.
$object = Invoke-RestMethod '[URI here]'
$object.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
$object
EWREST_supported_user0='Yes';
EWREST_updater_team='Support Team';
EWREST_eng_id='CLT';
EWREST_testlabelsub='Test Label';
EWREST_time_created='17:21:03';
EWREST_cr_conversion_related_to='Support Case';
EWREST__1901_full_name='secuser Testuser1';
EWREST_summary='Ticket Title';
EWREST_i_would_like_to_reopen_my_ticket='No';
EWREST_assigned_team_leader='Agiloft Admin';
EWREST_id='183255';
EWREST_severity='Sev 4';
EWREST_problem_description='<div>This is an example of a ticket note that takes up multiple lines when read via API<\/div><div> <\/div><div>Example note info here<\/div><div> <\/div>
<div>Additional example note info here<\/div><div> <\/div><div>Even more note info here<\/div>';
EWREST_demo_data='No';
What I would like to be able to do is manipulate $object as an object by doing things like $object.EWREST_category and get "Networking". So I have been trying to figure out how to maniuplate $object which is just a string of attributes to a traditional PowerShell object with properties.
Can someone offer some pointers on how to go about that?
Since you already have a string with key/value pairs I'd just do a little cleanup (remove the single quotes and the semicolons), convert the string to a hashtable, then build a custom object from that:
$response = Invoke-RestMethod '[URI here]'
$props = $response -replace "'" -replace ';' | ConvertFrom-StringData
$object = New-Object -Type PSObject -Property $props
Edit: To mangle the multiline value into one line you can use another replacement with a negative lookbehind assertion ((?<!...)) that removes newlines only if they're not preceded by a single quote followed by a semicolon. However, since that same property contains other semicolons you also need to modify the semicolon replacement, so that it only removes semicolons if they're followed by a newline or the end of the string (using a positive lookahead assertion, (?=...)).
$props = $response -replace "(?<!';)`n" -replace "'" -replace ";(?=`n|`$)" |
ConvertFrom-StringData
Maybe next naive script could suffice?
$ob= "EWREST_supported_user0='Yes';
EWREST_category='Networking';
EWREST_updater_team='Admin Team';
EWREST_time_created='12:56:53';
EWREST_cr_conversion_related_to='Support Case';
" # this is that string
# transform string to an array:
$oba = $ob.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
$obah=#{} # create empty hash table
# and fill it from array then:
$oba | ForEach-Object {
$aux=$_.split('=;') # key / value pair
$obah[$aux[0]] = $aux[1] } # hash[key] = value
$obah.Keys # display hash table keys (only debug)
$obah.EWREST_time_created # hash table item use (example: property)
$obah['EWREST_category'] # hash table item use (another approach: index)
Related
I am picking information from CSV file and i have mentioned tags like #{"R"="red";"B"="Blue"}.
when i am assigning tags value to variable , it is printing on same format but while adding tags to vm i am getting below error ,
Set-AzResource : Cannot bind parameter 'Tag'. Cannot convert the "System.Collections.Hashtable" value of type "System.String" to
$tags| convertfrom-stringdata
but the problem is after running add tag command for Vm , it is adding tag like below
#{"r : ="red";"B"="Blue"}
How can i add both tag as a separate like
r:red b:blue
$rss = Import-csv "C:\abc\VijayGupta\Desktop\Vm_build_azure.csv"
$tag = $rss.vmtags
$tags = $tag | ConvertFrom-StringData
$vms=Get-AzResource -Name abc -ResourceGroupName Southindia
Set-AzResource -ResourceId $vms.Id -Tag $tags -Force
If I understand the question, in your CSV file, there is a column called vmtags. The values in that column are strings in the form of #{"R"="red";"B"="Blue"}.
The Get-AzResource cmdlet wants a Hashtable object for its -Tags parameter. I think you took the description MS gives: Key-value pairs in the form of a hash table. For example: #{key0="value0";key1=$null;key2="value2"} a bit too literal there and now you need to create an actual Hashtable object from its string representation.
To create a Hashtable from a string like that you can use
# create a scriptblock using the string
$scriptBlock = [scriptblock]::Create('#{"R"="red";"B"="Blue"')
# execute it to create the hashtable
$tags = (& $scriptBlock)
$tags is now a Hashtable containing
Name Value
---- -----
R red
B Blue
If you need to create a Hashtable from multiple strings, do something like
$vmtags = '#{"R"="red";"B"="Blue"}', '#{"G"="green";"A"="Alpha"}'
# first loop creates the hashtables from the individual strings
$arr = $vmtags | ForEach-Object {
$scriptBlock = [scriptblock]::Create($_)
& $scriptBlock
}
# the second loop merges all Hashtables in the array into one
$tags = #{}
$arr | ForEach-Object {
foreach ($key in $_.Keys) {
$tags[$key] = $_.$key
}
}
$tags is now a Hashtable containing
Name Value
---- -----
R red
B Blue
A Alpha
G green
in my script I want to read a csv-file into an array and split the text in the first column.
The file consists a table with 2 columns. In the first column there are the personal names with the short Usernames in brackets. In the second column are the position of the user.
User:
Hoch,Susane (HOCH05)
Albrecht, Melanie (ALBRE05)
Department:
Managment
Salesoffice
I read the first column in an array and want to split every char after the first "(". So the I have got "Hoch, Susanne" instead of "Hoch, Susane (HOCH05)".
I get the following error message:
[Selected.System.Management.Automation.PSCustomObject] contains no method with the name "Split".
The type of the variable "$value is:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I canĀ“t find my misstake.
Here is my code:
$Arrayusername_ads_unique = New-Object System.Collections.ArrayList
$AD_User = New-Object System.Collections.ArrayList
$AD_User_table = New-Object System.Collections.ArrayList
$username_AD = New-Object System.Collections.ArrayList
$Arrayusername_ads_unique = Get-Content -Path "C:\temp\Userabgleich\Liste-original\User_ADS-utf8.csv"
$Arrayusername_ads_unique | Out-File C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv -Append -Encoding utf8
$AD_User = Import-CSV 'C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv' -Delimiter ";" | sort User
$AD_User_table = $AD_User | Select-Object User
foreach ($value in $AD_User_table)
{
$value.GetType()
$username_AD = $value.Split("(")
}
You can modify your foreach loop to achieve the results:
foreach ($value in $AD_User_table)
{
($value.user -split "\(")[0]
}
I am splitting on the .user property of $value to retrieve the value you are after in string format. By default, $value is going to be a [PSCustomObject] with a property called User. I am retrieving index 0 ([0]) because your -split match will consume a line of output whether or not you choose to keep the output.
If you are only looping to retrieve this particular result, you can accomplish this without a loop using regex substitution and named captures:
$ad_user_table.user -replace "(?<Name>.*?)\(.*",'${Name}'
since you did not provide a proper CSV to test against, i made a guess at what it would look like. [grin]
what it does ...
uses the .Split() method to split on the (
takes the 1st result of that split
trims away any leading/trailing whitespace
sends the result to the $Names collection
displays the content of that collection
here's the code ...
# fake reading in a CSV file
# in real life, use Import-CSV
$InStuff = #'
User,Department
"Hoch,Susane (HOCH05)","Managment"
"Albrecht, Melanie (ALBRE05)","Salesoffice"
'# | ConvertFrom-Csv
$Names = foreach ($IS_Item in $InStuff)
{
$IS_Item.User.Split('(')[0].Trim()
}
$Names
output ...
Hoch,Susane
Albrecht, Melanie
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.
I have tried different ways but not able to format data into table
$str1 = "First string"
$str2 = "Sec string"
$str3 = "third str"
$str4 = "fourth string"
$str = "$str1 $str2 `r`n"
$str+= "$str3 $str4"
write-host $str | Format-Table
I am looking to create output like below:
First string Sec string
third str fourth string
In order to use Format-Table as intended, you need objects with properties rather than mere strings:
$str -split "`r`n" | ForEach-Object {
# Initialize a custom object whose properties will reflect
# the input line's tokens (column values).
$obj = New-Object PSCustomObject; $i = 0
# Add each whitespace-separated token as a property.
foreach ($token in -split $_) {
Add-Member -InputObject $obj -NotePropertyName ('col' + ++$i) -NotePropertyValue $token
}
# Output the custom object.
$obj
} | Format-Table -HideTableHeaders
$str -split "`r`n" splits the multi-line string into individual lines and sends them through the pipeline one by one.
The ForEach-Object command constructs a custom object from each line whose properties are the whitespace-separated tokens on the line, as described in the comments; the property names - which don't matter for the output - are auto-generated as col1, col2, ...
Note: This does not match your desired output exactly in that each space (run of whitespace) is treated as a separator. If you wanted to treat the original $str1, $str2, ... variable values (e.g., First string) each as a single column value, you'd have to make assumptions about how to tokenize the line.
For instance, if the assumption is that 2 consecutive words form a single value, replace -split $_ above with $_ -split '(\w+ \w+) ?' -ne ''
If you didn't want to rely on assumptions, you'd have to construct your input strings with embedded quoting so as to unambiguously indicate token boundaries (the code would then have to be modified to parse the embedded quoting correctly).
Format-Table then displays the custom objects in tabular form, with columns properly aligned; -HideTableHeaders suppresses the header line (the auto-generated property names).
With your sample input, the above yields the following, produced without -HideTableHeaders so as to better illustrate what the code does:
col1 col2 col3 col4
---- ---- ---- ----
First string Sec string
third str fourth string
Ditto, but with the 2-consecutive-words splitting logic:
col1 col2
---- ----
First string Sec string
third str fourth string
As for what you tried:
Do not use Write-Host to produce data output: Write-Host output (by default) goes to the console and bypasses the pipeline, so that Format-Table receives no input and has no effect here.
That said, even if Format-Table did receive input (by using $str by itself, without Write-Host, i.e.: $str | Format-Table), it would have no (visible) effect on strings, which are always rendered as-is.
I have a csv with a list of usernames
I want to import just one cell of the csv file e.g. A2
Is it possible to be that specific? I have tried googling for this but don't see an exact solution. Tried powershell help also.
Can this be done ?
Thanks
Confuseis
The below example will select and output only 'cell' A2 from test.csv
In this example, the column header for row A is 'username'
$inFile = Import-Csv c:\Temp\test.csv
$targetCell = $inFile.username[1]
Write-Output $targetCell
This snippet is doing the following:
Import the csv file, yielding a PowerShell object.
Select the column you want to work with, the items from that column can be treated as an array. Select the desired item in that column by referring to it's zero based index value.
Output the results.
Import-CSV creates an array of objects from the input file. The column labels in the first row of the CSV become the property names. The other rows are objects in the array. Like any array you can call one element using brackets.
$arrUsers = Import-CSV c:\temp\users.csv
$arrUsers[1]
The second command, above, prints the second object, since counting starts with 0. This object came from the third line of the CSV file, since the first was used as column headers.
If you use Get-Member, it will show you the members (properties and methods) of an object.
$arrUsers | Get-Member
Assuming one of the members is username, combine this with array indexing, you can use:
$arrUsers[1].username
Import-CSV is a very flexible tool. Especially combined with Foreach and Export-CSV. Between Get-Help and Get-Member, you can explore Powershell with ease. Good luck.
When you use Import-Csv you convert the content into a PSCustomObject.
Examples on the following table:
PS> $csv = Import-Csv .\test.csv
PS> $csv
ProcessName Id WS CPU
----------- -- -- ---
sihost 5996 30015488 44.640625
pia_nw 11064 10620928 52.921875
pia_nw 2344 7933952 104.0625
RuntimeBroker 6500 77500416 177.34375
SettingSyncHost 6736 5074944 202.796875
explorer 6600 284934144 272.140625
ipoint 920 3162112 372.78125
rubyw 10648 18026496 389.46875
pia_nw 3108 31330304 1640.5625
OneDrive 10208 33206272 6422.4375
So you will need a NoteProperty name to call a value you're looking for.
PS> $csv.ProcessName[0]
sihost
Another way is to make a header array and use that to slice the data.
If working with a an object:
PS> $header = ($csv | ConvertTo-Csv -NoTypeInfo)[0] -replace '"' -split ",";
>>
PS> $header
ProcessName
Id
WS
CPU
Or if working with the file:
PS> $header = (gc .\test.csv)[0] -replace '"' -split ',';
ProcessName
Id
WS
CPU
Then just use the appropriate index:
PS> $csv[0]."$($header[0])"
sihost
Finally there is the Excel.Application ComObject method on an xlsx file. This will let you select cell's and ranges.
PS> $file = "C:\Some\Path\IMade\Up\test.xlsx"
PS> $objExcel = New-Object -ComObject Excel.Application
PS> $objExcel.Visible = $false
PS> $wb = $objExcel.Workbooks.Open($file)
PS> $ws = $wb.Sheets.Item(1)
PS> $ws.Range("A2").Text
sihost
More info on using the ComObjects can be found here:
Application Object (Excel)