My JSON
[
{
"solution": "abc",
"solutionName": "abc_test",
"solutionShortcode": "",
"isManaged": false
},
{
"solution": "def",
"solutionName": "def_test",
"solutionShortcode": "def1",
"isManaged": true
}
]
What I need to do is take the solutionName and the solutionShortcode from each and insert them into a new hashtable - i thought this would work...
$buildDictionary = #{}
$b = Get-Content -Raw -Path ./temp.json | ConvertFrom-Json
foreach ($a in $b.solutionName.GetEnumerator()) {
$name = $b.solutionName.toString()
$shortcode = $b.solutionShortcode.toString()
if ([string]::IsNullOrEmpty($shortcode)) {
$shortcode = "0xxx"
}
$buildDictionary.Add("$name","$shortcode")
}
Write-Output $buildDictionary
Basically what I need to do is not that complex but there must be something I am missing about the ConvertFrom-Json cmdlet because this is not working as expected.
Essentially I need to add a generic value if the "shortcode" is empty and take insert the solutionName and solutionShortcode into the "buildDictionary" hashtable as a key/value pair.
What I have currently errors with "Key already added" error messages.. my resulting hashtable ends up with a single row consisting of a System.Object and not a key=value pair with string content.
Would appreciate some insight into what is wrong and why it is wrong.
Thanks!
...should do what you want:
$buildDictionary = #{}
$json = ConvertFrom-Json -InputObject (gc '.\temp.json' -raw)
$json | %{
If (!$_.solutionShortCode){
$_.solutionShortCode = 'someValue'
$buildDictionary.add($_.solutionName,$_.solutionShortcode)
}
}
If solutionShortCode is empty it sets 'someValue' as value and adds solutionName as Key to the HashTable $buildDictionary with the value solutionShortCode.
Related
I am trying to create array made by PSCustomObject elements, but having some issues with it. Here is the simplified example of the code I have:
$FineData = #()
foreach ($Session in $Sessions) {
$Job = Get-VBRTaskSession -Session $Session
$Result = [pscustomobject]#{
Id = $job.Id.Guid
}
$FineData += $Result
}
$FineData
The format it returns:
Id
{19ea68a7-f2c3-4429-bc46-a87b4a295105, 3ebf8568-10ce-4608-bbb2-c80d06874173, 2ec28852-3f5e-477d-8742-872863e41b6d}
The format I want:
Id : 19ea68a7-f2c3-4429-bc46-a87b4a295105
Id : 3ebf8568-10ce-4608-bbb2-c80d06874173
Id : 2ec28852-3f5e-477d-8742-872863e41b6d
Thanks in advance.
It seems like your Get-VBRTaskSession could be returning more than one object, hence an inner loop is required:
$FineData = foreach ($Session in $Sessions) {
foreach($guid in (Get-VBRTaskSession -Session $Session).Id.Guid) {
[pscustomobject]#{
Id = $guid
}
}
}
$FineData
You can then pipe $FineData to Format-List if you want it to be displayed as a list in your console:
$FineData | Format-List
As aside, adding to a fixed collection should be avoided, see this answer for details: Why should I avoid using the increase assignment operator (+=) to create a collection.
I have an array that contains different rows where one column identifies the "record" "type." I want to iterate through this array and sort each item based on that value into a new array so that I have one array per type.
Here's what I have so far:
$data = Get-ADObject -SearchBase $sb -filter * -properties * | select samaccountname,canonicalname,objectclass,distinguishedname | sort objectclass,samaccountname
$oct = $data | select objectclass -Unique
foreach ($o in $oct)
{
$oc = $o.objectclass
Remove-Variable -name "$oc"
New-Variable -name "$oc" -value #()
}
$d = #()
$user = #()
foreach ($d in $data)
{
$oc = $d.objectclass
foreach ($o in $oct)
{
$1 = $o.objectclass
if ($1 -eq $oc)
{
('$' + $oc) += $d
}
}
}
(the lines: Remove-Variable -name "$oc", $d = #(), and $user = #() are for testing purposes so ignore those)
This works great up to the line where I try to dynamically reference my new arrays. What am I doing wrong and how can I fix it?
The error text is:
('$' + $oc) += $d
~~~~~~~~~ The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept
assignments, such as a variable or a property.
CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : InvalidLeftHandSide
I have tried using $($oc), but that didn't work either. If I change it to the name of one of my dynamically created arrays like $user, the code works fine except that it loads everything into the $user array (obviously).
The reason I tried ('$' + $oc) is because this is the only way I could get ISE to output $user.
I also tried ('$' + $oc).add($d) but it appears to be seeing it as a string rather than the array.
Any pointers are appreciated.
Use the Get-Variable and Set-Variable cmdlets:
$curVal = Get-Variable -Name $oc -ValueOnly
Set-Variable -Name $oc -Value ($curVal+$d)
But note that you would be better off building this array in a local variable first, and then assigning it to your "runtime-named" variable once, as these get and set operations are going to be way slower.
Rather than fiddling around with dynamically named variables, I'd use dictionary-type, like for example a hashtable:
# initialize an empty hashtable
$objectsByClass = #{}
# Define list of properties
$properties = 'samaccountname','canonicalname','objectclass','distinguishedname'
# Retrieve AD objects
$Data = Get-ADObject -SearchBase $sb -filter * -properties $properties | select $properties | sort objectclass,samaccountname
#Populate hashtable
$Data |ForEach-Object {
if(-not $objectsByClass.ContainsKey($_.objectClass)){
# Create entry in hashtable
$objectsByClass[$_.objectClass] = #()
}
# Add entry to dictionary
$objectsByClass[$_.objectClass] += $_
}
Now you can access the items by class name:
$users = $objectsByClass['user']
And you can easily discover all class names:
$classNames = $objectsByClass.Keys
As briantist points out, you can also have Group-Object build the hashtable for you if the above gets too verbose:
$objectsByClass = $Data |Group-Object objectClass -AsHashTable
I'm trying to modify a badly formated JSON Textfile, that currently looks like this:
[
["val1", "val2", "val3", "val4"],
["val5", "val6", "val7", "val8"],
["val9", "val10", "val11", "val12"]
]
and I have an other array containing field names
["title1", "title2", "title3", "title4"]
I want to output a final.json textfile looking like this:
[
{"title1": "val1", "title2": "val2", "title3": "val3", "title4": "val4"},
{"title1": "val5", "title2": "val6", "title3": "val7", "title4": "val8"},
{"title1": "val9", "title2": "val10", "title3": "val11", "title4": "val12"}
]
I guess the best way would be to take each row, split by , and then adding them back together foreach-ing over the title names, but I'm not quite sure on how to do this in PowerShell.
Since you're dealing with structured data here, I think the best way is to parse the JSON, and work with the resulting objects. Create the objects you want, then convert back to JSON:
$j1 = #'
[
["val1", "val2", "val3", "val4"],
["val5", "val6", "val7", "val8"],
["val9", "val10", "val11", "val12"]
]
'#
$j2 = #'
["title1", "title2", "title3", "title4"]
'#
$a1 = $j1 | ConvertFrom-Json
$a2 = $j2 | ConvertFrom-Json
0..($a1.Count-1) | ForEach-Object {
$i = $_
$props = #{}
0..($a2.Count-1) | ForEach-Object {
$props[$a2[$_]] = $a1[$i][$_]
}
New-Object PSOBject -Property $props
} | ConvertTo-Json
ConvertTo-Json and ConvertFrom-Json are the cmdlets you need to serialize/deserialize the JSON. Then you just work with the objects.
In this case I'm going through each top level array in $a1 and creating a hashtable that contains the properties you want. Then I'm creating a PSObject with those properties. That gets returned from the ForEach-Object cmdlet (the result is an array of those objects) which then gets piped directly into ConvertTo-Json to give you the output needed.
I think a better approach is to read in the first JSON format via ConvertFrom-Json, then take that array of arrays and for each row, create a PSCustomObject from a hashtable e.g. [PSCustomObject]#{title1=$arr[$row][0]; title2=$arr[$row][1];...}. Once you then have that array of PSCustomObject, convert that back to JSON with ConvertTo-Json.
I am trying to parse robocopy log files to get file size, path, and date modified. I am getting the information via regex with no issues. However, for some reason, I am getting an array with a single element, and that element contains 3 hashes. My terminology might be off; I am still learning about hashes. What I want is a regular array with multple elements.
Output that I am getting:
FileSize FilePath DateTime
-------- -------- --------
{23040, 36864, 27136, 24064...} {\\server1\folder\Test File R... {2006/03/15 21:08:01, 2010/12...
As you can see, there is only one row, but that row contains multiple items. I want multiple rows.
Here is my code:
[regex]$Match_Regex = "^.{13}\s\d{4}/\d{2}/\d{2}\s\d{2}:\d{2}:\d{2}\s.*$"
[regex]$Replace_Regex = "^\s*([\d\.]*\s{0,1}\w{0,1})\s(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s(.*)$"
$MainContent = New-Object System.Collections.Generic.List[PSCustomObject]
Get-Content $Path\$InFile -ReadCount $Batch | ForEach-Object {
$FileSize = $_ -match $Match_Regex -replace $Replace_Regex,('$1').Trim()
$DateTime = $_ -match $Match_Regex -replace $Replace_Regex,('$2').Trim()
$FilePath = $_ -match $Match_Regex -replace $Replace_Regex,('$3').Trim()
$Props = #{
FileSize = $FileSize;
DateTime = $DateTime;
FilePath = $FilePath
}
$Obj = [PSCustomObject]$Props
$MainContent.Add($Obj)
}
$MainContent | % {
$_
}
What am I doing wrong? I am just not getting it. Thanks.
Note: This needs to be as fast as possible because I have to process millions of lines, which is why I am trying System.Collections.Generic.List.
I think the problem is that for what you're doing you actually need two foreach-object loops. Using Get-Content with -Readcount is going to give you an array of arrays. Use the -Match in the first Foreach-Object to filter out the records that match in each array. That's going to give you an array of the matched records. Then you need to foreach through that array to create one object for each record:
[regex]$Match_Regex = "^.{13}\s\d{4}/\d{2}/\d{2}\s\d{2}:\d{2}:\d{2}\s.*$"
[regex]$Replace_Regex = "^\s*([\d\.]*\s{0,1}\w{0,1})\s(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s(.*)$"
$MainContent =
Get-Content $Path\$InFile -ReadCount $Batch |
ForEach-Object {
$_ -match $Match_Regex |
ForEach-Object {
$FileSize = $_ -replace $Replace_Regex,('$1').Trim()
$DateTime = $_ -replace $Replace_Regex,('$2').Trim()
$FilePath = $_ -replace $Replace_Regex,('$3').Trim()
[PSCustomObject]#{
FileSize = $FileSize
DateTime = $DateTime
FilePath = $FilePath
}
}
}
You don't really need to use the collection as an accumulator, just output PSCustomObjects, and let them accumulate in the result variable.
I'm using Powershell 1.0 to remove an item from an Array. Here's my script:
param (
[string]$backupDir = $(throw "Please supply the directory to housekeep"),
[int]$maxAge = 30,
[switch]$NoRecurse,
[switch]$KeepDirectories
)
$days = $maxAge * -1
# do not delete directories with these values in the path
$exclusionList = Get-Content HousekeepBackupsExclusions.txt
if ($NoRecurse)
{
$filesToDelete = Get-ChildItem $backupDir | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
else
{
$filesToDelete = Get-ChildItem $backupDir -Recurse | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
foreach ($file in $filesToDelete)
{
# remove the file from the deleted list if it's an exclusion
foreach ($exclusion in $exclusionList)
{
"Testing to see if $exclusion is in " + $file.FullName
if ($file.FullName.Contains($exclusion)) {$filesToDelete.Remove($file); "FOUND ONE!"}
}
}
I realize that Get-ChildItem in powershell returns a System.Array type. I therefore get this error when trying to use the Remove method:
Method invocation failed because [System.Object[]] doesn't contain a method named 'Remove'.
What I'd like to do is convert $filesToDelete to an ArrayList and then remove items using ArrayList.Remove. Is this a good idea or should I directly manipulate $filesToDelete as a System.Array in some way?
Thanks
The best way to do this is to use Where-Object to perform the filtering and use the returned array.
You can also use #splat to pass multiple parameters to a command (new in V2). If you cannot upgrade (and you should if at all possible, then just collect the output from Get-ChildItems (only repeating that one CmdLet) and do all the filtering in common code).
The working part of your script becomes:
$moreArgs = #{}
if (-not $NoRecurse) {
$moreArgs["Recurse"] = $true
}
$filesToDelete = Get-ChildItem $BackupDir #moreArgs |
where-object {-not $_.PsIsContainer -and
$_.LastWriteTime -lt $(Get-Date).AddDays($days) -and
-not $_.FullName.Contains($exclusion)}
In PSH arrays are immutable, you cannot modify them, but it very easy to create a new one (operators like += on arrays actually create a new array and return that).
I agree with Richard, that Where-Object should be used here. However, it's harder to read.
What I would propose:
# get $filesToDelete and #exclusionList. In V2 use splatting as proposed by Richard.
$res = $filesToDelete | % {
$file = $_
$isExcluded = ($exclusionList | % { $file.FullName.Contains($_) } )
if (!$isExcluded) {
$file
}
}
#the files are in $res
Also note that generally it is not possible to iterate over a collection and change it. You would get an exception.
$a = New-Object System.Collections.ArrayList
$a.AddRange((1,2,3))
foreach($item in $a) { $a.Add($item*$item) }
An error occurred while enumerating through a collection:
At line:1 char:8
+ foreach <<<< ($item in $a) { $a.Add($item*$item) }
+ CategoryInfo : InvalidOperation: (System.Collecti...numeratorSimple:ArrayListEnumeratorSimple) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
This is ancient. But, I wrote these a while ago to add and remove from powershell lists using recursion. It leverages the ability of powershell to do multiple assignment . That is, you can do $a,$b,$c=#('a','b','c') to assign a b and c to their variables. Doing $a,$b=#('a','b','c') assigns 'a' to $a and #('b','c') to $b.
First is by item value. It'll remove the first occurrence.
function Remove-ItemFromList ($Item,[array]$List(throw"the item $item was not in the list"),[array]$chckd_list=#())
{
if ($list.length -lt 1 ) { throw "the item $item was not in the list" }
$check_item,$temp_list=$list
if ($check_item -eq $item )
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-ItemFromList -item $item -chckd_list $chckd_list -list $temp_list )
}
}
This one removes by index. You can probably mess it up good by passing a value to count in the initial call.
function Remove-IndexFromList ([int]$Index,[array]$List,[array]$chckd_list=#(),[int]$count=0)
{
if (($list.length+$count-1) -lt $index )
{ throw "the index is out of range" }
$check_item,$temp_list=$list
if ($count -eq $index)
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-IndexFromList -count ($count + 1) -index $index -chckd_list $chckd_list -list $temp_list )
}
}
This is a very old question, but the problem is still valid, but none of the answers fit my scenario, so I will suggest another solution.
I my case, I read in an xml configuration file and I want to remove an element from an array.
[xml]$content = get-content $file
$element = $content.PathToArray | Where-Object {$_.name -eq "ElementToRemove" }
$element.ParentNode.RemoveChild($element)
This is very simple and gets the job done.