How do I get an array of strings from a powershell pipeline? - arrays

I have a list of directory names that I want to convert to absolute paths, and strip out any invalid ones. My initial attempt at doing this was the pipeline
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
However, what I get back has type [Object[]] rather than [String[]]. I tried ensuring that the paths existed (by adding a ? { Test-Path $_ } step to the pipeline, but that didn't help.
What am I doing wrong? How do I get the directories as a list of strings? I need this so that I can concatenate the array to another array of strings, specifically
$newpath = (($env:PATH -split ';'), $paths) -join ';'

object[] is simply the default array in PowerShell, and it doesn't matter in this (and most) situations. You problem is that you're trying to join to arrays using (arr1, arr2). What this acutally does is create an array with two array objects, because , is an array construction operator. Try to join the arrays using +, like this:
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = (($env:PATH -split ';') + $paths) -join ';'
and you could even skip the splitting, and just do
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = "$env:PATH;$($paths -join ';')"

This seems to work:
$newpath = (#($env:PATH) + $paths) -join ';'
You don't really need to explicitly cast it as [string[]]. Powershell will figure that out from the command context and coerce the data to the proper type.

Related

Powershell Editing Strings in Array

My array has a lot of properties but I'm only looking to edit one. The goal is to remove domains from the hostname but I haven't been able to get it working. This data is being returned from a REST API and there are thousands of assets that contain the same type of data (JSON content). The end goal is to compare assets pulled from the API and compare it to assets in a CSV file. The issue is that the domain may appear on one list and not the other so I'm trying to strip the domains off for comparison. I didn't want to iterate through both files and the comparison has to go from the CSV file to the API data hence the need to get rid of the domain altogether.
There are other properties in the array that I will need to pull from later. This is just an example of one of the arrays with a few properties:
$array = #{"description"="data"; "name"="host1.domain1.com"; "model"="data"; "ip"="10.0.0.1"; "make"="Microsoft"}
#{"description"="data"; "name"="host2.domain2.com"; "model"="data"; "ip"="10.0.0.2"; "make"="Different"}
#{"description"="data"; "name"="10.0.0.5"; "model"="data"; "ip"="10.0.0.5"; "make"="Different"}
The plan was to match the domain and then strip using the period.
$domainList = #(".domain1.com", ".domain2.com")
$domains = $domainList.ForEach{[Regex]::Escape($_)} -join '|'
$array = $array | ForEach-Object {
if($_.name -match $domains) {
$_.name = $_.name -replace $domains }
$_.name
}
You may do the following to output a new array of values without the domain names:
# starting array
$array = #("hosta.domain1.com", "hostb.domain2.com", "host3", "10.0.0.1")
# domains to remove
$domains = #('.domain1.com','.domain2.com')
# create regex expression with alternations: item1|item2|item3 etc.
# regex must escape literal . since dot has special meaning in regex
$regex = $domains.foreach{[regex]::Escape($_)} -join '|'
# replace domain strings and output everything else
$newArray = $array -replace $regex
Arrays use pointers so I needed to load the array and pipe through ForEach-Object and then set the object when logic was complete. Thanks for all of the help.
$domainList = #(".domain1.com", ".domain2.com")
$domains = $domainList.ForEach{[Regex]::Escape($_)} -join '|'
$array = $array | ForEach-Object {
if($_.name -match $domains) {
$_.name = $_.name -replace $domains }
$_.name
}

Powershell Get-Content + Array

how's it going?
I'm new on Powershell and I'm trying to simplify my code in order that I need to perform the same action in two files, the only thing that changes is the File Name and ReadCount size (15000 for the first file and 50000 for the second one).
When I run it the error shows:
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0].csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:2 char:7
+ $i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeA ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0]_1.csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:3 char:20
+ ... bookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Selec ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
That's the code, not sure if I'm accessing the Array using the right way on Powershell.
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$fileArray[$len]_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$fileArray[$len]_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$fileArray[$len]_$j.csv"
}
}
Any ideias?
Thanks a lot
The problem here is with string interpolation. A variable name within a string will expand up until it reaches a special character in that name. Then it will append the remainder of the string and any interpolated strings afterwards. This commonly happens with the . character when accessing a property of an object within a string. A simple solution is to use the subexpression operator ($()).
Get-Content "C:\Folder\$($fileArray[$len]).csv"
An alternative is to build the path string another way and then pass it into the command. The method below uses the format operator (-f).
$Path = "C:\Folder\{0}.csv" -f $fileArray[$len]
Get-Content $Path
Your code with the subexpression operator added will look like the following:
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$($fileArray[$len]).csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$($fileArray[$len])_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$($fileArray[$len])_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$($fileArray[$len])_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$($fileArray[$len])_$j.csv"
}
}
You can see this behavior on a simpler scale using your $fileArray variable.
$filearray
08_configuration_items
11_CI-Contract-new
# Notice how the [0] gets appended to the string-cast $fileArray
"$filearray[0]"
08_configuration_items 11_CI-Contract-new[0]
$filearray[0]
08_configuration_items
"$($filearray[0])"
08_configuration_items
Since $fileArray is an array of strings, you have another unintended effect. With "$fileArray[0]", $fileArray will be interpolated and converted to a string output rather than an array. PowerShell by default will join array elements by a single space when casting as a string. So the resulting output format is arrayItem1 arrayItem2 arrayItem3[0]. [0] is not included as part of the variable evaluation.

How to dynamically reference a powershell variable

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

Adding objects to an array in a hashtable

I want to create a Hashtable which groups files with the same name in arrays so I can later on work with those to list some properties of those files, like the folders where they're stored.
$ht = #{}
gci -recurse -file | % {
try{
$ht.Add($_.Name,#())
$ht[$_.Name] += $_
}
catch{
$ht[$_.Name] += $_
}
}
All I'm getting is:
Index operation failed; the array index evaluated to null.
At line:8 char:13
+ $ht[$_.Name] += $_
+ ~~~~~~~~~~~~~~~~~~
I'm not sure why this isn't working, I'd appreciate any help.
Don't reinvent the wheel. You want to group files with the same name, use the Group-Object cmdlet:
$groupedFiles = Get-ChildItem -recurse -file | Group-Object Name
Now you can easy retrieve all file names that are present at least twice using the Where-Object cmdlet:
$groupedFiles | Where-Object Count -gt 1
You are getting this error because if your code hits the catch block, the current pipeline variable ($_) represents the last error and not the current item. You can fix that by either storing the current item an a variable, or you use the -PipelineVariable advanced cmdlet parameter:
$ht = #{}
gci -recurse -file -PipelineVariable item | % {
try{
$ht.Add($item.Name,#())
$ht[$item.Name] += $item
}
catch{
$ht[$item.Name] += $item
}
}

One element containing hashes instead of multiple elements - how to fix?

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.

Resources