Append Member/Column to PSObject - powershell

I'm looking for a way to add an extra member to an object in PowerShell without creating a new object and looping.
Typically when I run a script it will be again a list of servers and I want to append some extra information against the data I'm returning but I can't find a way to do it without creating a new PSObject and looping through the existing object and adding the extra member row by row (simplified example below)
$FileList = get-childitem | Select-Object Name, DirectoryName
$FileListOBJ = #()
foreach ($item in $FileList)
{
$Temp = New-Object PSObject
$Temp | Add-Member NoteProperty ServerName "XServerName"
$Temp | Add-Member NoteProperty FileName $item.Name
$Temp | Add-Member NoteProperty Directory $item.DirectoryName
$FileListOBJ += $Temp
}
$FileListOBJ
Is there a way to do it along these lines ...
get-childitem | Select-Object "ServerName", Name, DirectoryName
The above code creates the extra Member but I haven't been able to find a way to fill the additional member with the details I'm after.

you can also create a new object without the add-member calls:
$FileList = get-childitem | Select-Object Name, DirectoryName
$FileListOBJ = #()
foreach ($item in $FileList)
{
$FileListOBJ += [PSCustomObject]#{ServerName="ServerName";FileName=$item.Name;Directory=$item.DirectoryName}
}
$FileListOBJ

Sure, you can use a calculated property (more about them here):
get-childitem | Select-Object #{l="ServerName"; e={"XServerName"}}, Name, DirectoryName

Related

How to get Folder stats to a csv file for a specific year (like 2022)

This script runs fine but I don't know how to add something like to get just 2022 last edited files only like {where LastWriteTime -eq 2022}
$arr = #()
gci 'C:\Users\myusername\Documents\' -recurse | ? {$_.PSIsContainer -eq $False} | % {
$obj = New-Object PSObject
$obj | Add-Member NoteProperty Directory $_.DirectoryName
$obj | Add-Member NoteProperty Name $_.Name
$obj | Add-Member NoteProperty Length $_.Length
$obj | Add-Member NoteProperty CreationTime $_.CreationTime
$obj | Add-Member NoteProperty Access $_.LastAccessTime
$obj | Add-Member NoteProperty Owner ((Get-ACL $_.FullName).Owner)
$arr += $obj
}
$arr | Export-CSV -notypeinformation "C:\Users\myusername\Downloads\report.csv"
LastWriteTime is a DateTime instance, hence you can simply check the Year property and compare against it. As for your code, 3 recommendations:
Use -File or -Directory when needing to filter by any of them.
Use PSCustomObject to instantiate your objects. It's easier and faster.
Avoid adding elements += to a fixed collection #(). You can take full advantage of the pipeline in this case, get the files one by one, process each object and output them to a file.
Get-ChildItem 'C:\Users\myusername\Documents\' -Recurse -File | ForEach-Object {
if($_.LastWriteTime.Year -eq 2022) {
[pscustomobject]#{
Directory = $_.DirectoryName
Name = $_.Name
Length = $_.Length
CreationTime = $_.CreationTime
Access = $_.LastAccessTime
Owner = (Get-ACL $_.FullName).Owner
}
}
} | Export-CSV "C:\Users\myusername\Downloads\report.csv" -NoTypeInformation
Santiago's helpful answer contains an effective solution and explains the problems with your approach well.
Let me complement it with an alternative solution that uses the Where-Object cmdlet to perform the desired filtering first, and the Select-Object cmdlet with calculated properties to get the desired property names and values to pass to Export-Csv:
Get-ChildItem -File C:\Users\myusername\Documents -recurse |
Where-Object { $_.LastWriteTime.Year -eq 2022 } |
Select-Object #{ Name='Directory'; Expression = { $_.DirectoryName } },
Name,
Length,
CreationTime,
#{ Name='Access'; Expression = { $_.LastAccessTime } },
#{ Name='Owner'; Expression = { (Get-ACL $_.FullName).Owner } } |
Export-CSV -notypeinformation "C:\Users\myusername\Downloads\report.csv"

Prevent modification of original array in a loop (foreach) - powershell

I need some assistance please, to understand behavior of arrays and powershell loops.
I have stumbled upon a strange array behavior, here is the issue and sample script:
# I create simple array
$item2 = New-Object Collections.ArrayList
# getting some sample data for array
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
#running loop
foreach ($item in $allitems){
# outputing iriginal array item
Write-Host $item
# addind to new array
$item2.Add($item)| Out-Null
# here is the issue I have - after I add member to my $item2 array
# it is replicated to also $allitems, I don't need this behavior
# to happen. Also I don't understand why it changes the original
# $item and $allitems. Am I creating array copy incorrectly ?
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
write-host "$($item2[-1]) mod"
Write-Host $item
}
result of the script is this :
#{Name=rollback2.puc; Attributes=Archive} -> original entry
#{Name=rollback2.puc; Attributes=Archive; testproperty=testvalue} mod -> modified entry
#{Name=rollback2.puc; Attributes=Archive; testproperty=testvalue} -> original entry modified , why ?
Any help appreciated.
Thanks
Most types of objects in .NET are reference-type objects, meaning that what you have stored in $item is not the object itself, but a reference to it.
When you then call $item2.Add($item), the reference is copied to $item2, but both the array list and the variable still point back to the exact same object/memory address, and PowerShell will therefore "remember" the added property regardless of which copy of the object reference you use.
In this particular case, you can work around it and create 2 sets of objects by calling Get-ChildItem twice:
$item2 = New-Object Collections.ArrayList
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
foreach ($item in Get-ChildItem -Path "c:\" | select name,Attributes){
Write-Host $item
$item2.Add($item)| Out-Null
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
# the objects stored in $allitems will have been unaffected by the call above
}
Or by calling Get-Item to create a new object to represent the file just before adding to the array list:
$item2 = New-Object Collections.ArrayList
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
#running loop
foreach ($item in $allitems){
Write-Host $item
# Create a new FileInfo object by calling `Get-Item`
$itemCopy = $item | Get-Item
$item2.Add($itemCopy) | Out-Null
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
# $item2[-1] has been modified, but $item has not
}

Add column CSV in existing CSV with powershell

I have a Powershell script, which collects the size of a backup, and exports it to CSV, I would like to know if it is possible that it could be added to the next csv column, or an excel.
I've been looking at the documentation, because I think it looks better on an excel, but I can't add one more column, I always believe it from scratch.
$today = (get-date).Date
$backup = Get-VBRBackup | where {$_.info.jobname -eq "A. ProduccionInterna.Infraestructura Backup Copy"}
if ($backup) {
$backup.GetAllStorages() | where {$_.CreationTime.Date -eq $today} | select {$_.PartialPath}, {$_.Stats.BackupSize/1GB} |
export-csv -Path C:\Users\acepero\Documents\test.csv -NoTypeInformation -Delimiter ';'
}
UPDATE
I have managed to create new columns once, then it gives an error:
Select-Object : The property cannot be processed because the property "{$_.PartialPath}, {$_.Stats.BackupSize/1GB} , {$Session.BackupStats.DedupRatio} ,
{$Session.BackupStats.CompressRatio}" already exists.
The code now has this form
$today = (get-date).Date
$backup = Get-VBRBackup | where {$_.info.jobname -eq "A. ProduccionInterna.Infraestructura Backup Copy"}
if ($backup) {
$backup.GetAllStorages() | where {$_.CreationTime.Date -eq $today} | select {$_.PartialPath}, {$_.Stats.BackupSize/1GB} , {$Session.BackupStats.DedupRatio} , {$Session.BackupStats.CompressRatio}
(Import-Csv "C:\Users\acepero\Documents\test.csv") |
Select-Object *, {{$_.PartialPath}, {$_.Stats.BackupSize/1GB} , {$Session.BackupStats.DedupRatio} , {$Session.BackupStats.CompressRatio}} |
Export-csv -Path C:\Users\acepero\Documents\test.csv -NoTypeInformation #-Delimiter ';'
}
When you take output from a command and pipe it through select, you are creating an output object, which has the selected values as properties. Here is an example using the Get-ChildItem command:
$result = Get-ChildItem C:\Temp | select Name, Length
The $result array contains objects which have the "Length" and "Name" NoteProperties. When you pipe that object to Export-CSV, it creates one column for each Property/NoteProperty the object has. In order to 'add a column to the CSV', all you need to do is add a NoteProperty to the object. You can do that with the Add-Member cmdlet, like this:
$result | Add-Member -MemberType NoteProperty -Name 'ColumnName' -Value 'ColumnValue'
Be careful how you do this. If $result is a single object, this command will add the NoteProperty/Value pair to that object. If $result is an array of objects, it will add that NoteProperty/Value pair to all objects held in the array. If you need to assign different values to each object, you'll need to iterate through the array:
ForEach ($res in $result)
{
$thisvalue = '' #Assign specific value here
$res | Add-Member -MemberType NoteProperty -Name 'ColumnName' -Value $thisvalue
}
I hope this helps you. If it does, please don't forget to accept the answer.

Create Table showing Foldername and Count of files

I'm trying to create a script that, based on a list of directories, produces a list or table showing 'Folder Name' and 'Count' of files contained within each folder.
I have the following so far:
$folders = #("c:\temp", "C:\temp\tosh")
foreach ($folder in $folders) {
$folderFiles = Get-ChildItem -Path $folder -File
$obj = New-Object psobject -Property #{
Folder = $folder
Count = $folderFiles.count
}
$obj | Format-Table
}
This works but produces effectively two tables, one for each folder. I need this as one table but when I try to add another row to the object I get an error that PSObject does not contain a method named 'op_Addition'. I'm sure that I've had a similar result before by adding the $obj object to an array at each iteration similar to this:
$results += $obj
But can't seem to get it working at present. Any advice appreciated, thanks.
You need to define $results first as type array and then you can add values to the array. If you don't do so, then $results will be System.Object BaseType and hence the error. Try this.
$folders = #("c:\temp", "C:\temp\tosh")
$results = #()
foreach ($folder in $folders) {
$folderFiles = Get-ChildItem -Path $folder -File
$obj = New-Object psobject -Property #{
Folder = $folder
Count = $folderFiles.count
}
$results += $obj
}
$results
Vivek's answer will get you what you're after.
You can simply this further, avoiding the need to initialise an empty array and to use a temporary object. This uses the pscsutomobject type accelerator.
$folders = #("c:\temp", "C:\temp\tosh")
foreach ($folder in $folders) {
[array]$results += [pscustomobject]#{
Folder = $folder
Count = (Get-ChildItem -Path $folder -File).count
}
}
$results
I see that you have$obj | Format-Table in your loop. You could have $results | Format-Table at the end instead.
ls \temp,\temp\tosh\ | group psparentpath | select count,name

Powershell. Need help importing hostnames from .csv. The rest of this works

I am trying to figure out how to correct this script I've wrote. I know it is something wrong with the way it is importing the list of hostnames. I don't know how to fix it.
Part 1: This is supposed to import a .csv with the hostnames and dig the registry for the application's uninstall information, put it into an array, and export into .csv's for later use. Also it creates .txt files in order to later compare the applications on the system to a baseline.
$path = "\\path"
$computers = Import-Csv -Path "\\Path\hostnames.csv"
$array = #()
foreach($pc in $computers)
{
$computername = $pc.computername
#$computername = "KNOWN_HOSTNAME" #test line for one system
$UninstallKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
$regkey = $reg.OpenSubKey($UninstallKey)
$subkeys = $regkey.GetSubKeyNames()
foreach($key in $subkeys)
{
$thisKey=$UninstallKey+"\\"+$key
$thisSubKey=$reg.OpenSubKey($thisKey)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallDate" -Value $($thisSubKey.GetValue("InstallDate"))
$array += $obj
$ExportArray = $array | Where-Object { $_.DisplayName } |
select ComputerName , DisplayName, DisplayVersion, Publisher, InstallDate
$ExportArray |
Export-csv $path\$computername.csv -NoTypeInformation
$ExportArray2 = $array |
Where-Object { $_.DisplayName } |
select DisplayName, DisplayVersion, Publisher
$ExportArray2 |
Export-csv $path\$computername.txt -NoTypeInformation
}
}
Part 2: This portion compiles the .csv's into one excel document for reporting
$csvs = Get-ChildItem $path\* -Include *.csv
$outputfilename = "Network_" + (Get-Date -Format yyyyMMdd)
$excelapp = new-object -comobject Excel.Application
$excelapp.sheetsInNewWorkbook = $csvs.Count
$xlsx = $excelapp.Workbooks.Add()
$sheet=1
foreach ($csv in $csvs)
{
$row=1
$column=1
$worksheet = $xlsx.Worksheets.Item($sheet)
$worksheet.Name = $csv.Name
$file = (Get-Content $csv.PSPath | ForEach-Object {$_ -replace '"', ""})
foreach($line in $file)
{
$linecontents = $line -split ‘,(?!\s*\w+”)’
foreach($cell in $linecontents)
{
$worksheet.Cells.Item($row,$column) = $cell
$column++
}
$column = 1
$row++
}
$sheet++
}
$output = $path + “\” + $outputfilename + ".xlsx"
$xlsx.SaveAs($output)
$excelapp.quit()
Part 3: This portion loads up a baseline, and the .txt's created preciously, and checks for differences in the files. (also deletes blank ouput files)
$bline = Get-ChildItem $path\* -Include Baseline.txt
$txts = Get-ChildItem $path\* -Include *.txt -Exclude Baseline.txt
foreach ($txt in $txts)
{
Compare-Object -referenceobject $(Get-Content $bline) -differenceobject $(Get-Content $txt) |
ft inputobject, #{n = "file"; e = {if ($_.SideIndicator -eq '=>') {"System"} else {"Baseline"}}} |
Out-File $txt'_has_diff'.csv -Width 256
Get-ChildItem $path |
where {$_.Length -eq 0} |
Remove-Item
}
Thank you
Edit:
The Hostnames.csv files I've tried are:
HOSTNAME1
HOSTNAME2
and
"HOSTNAME1","HOSTNAME2"
It's a little unclear what the problem is, because you say there is "something wrong with the way it is importing the list of hostnames", but you haven't specified what kind of results you're getting and how they differ from the intended results.
However, based on your sample data I think I can infer what the problem is: You're trying to use Import-Csv on non-CSV data. Neither of your examples looks like a CSV file. They both look like lists. A list in which the items are separated by commas, such as
"HOSTNAME1","HOSTNAME2","HOSTNAME3","HOSTNAME4"
is not called a "CSV file". CSV files are a form of "flat file", in which the data represents the rows and columns of a single database table. An example of a CSV file would be something like this, where the first line is a list of field (column) names, and the other lines are records (rows) with the comma-separated values corresponding to the columns in the header row:
"Hostname","OS","OS Version","Primary Function","Location"
"BOSEXCH01","Windows","Server 2012","Microsoft Exchange","Boston"
"BOSDC01","Windows","Server 2008 R2","Active Directory domain controller","Boston"
"MYWEB","Linux","Ubuntu 13.04","Apache web server","Phoenix"
The cmdlet Import-Csv imports a CSV file into an array of objects in which the properties are the field names in the header row, and the values are the comma-separated items in each row corresponding to the property names derived from the header row. Export-Csv does the reverse—it creates a CSV file from an array of objects.
It looks like what you're trying to do is read a simple list of hostnames into an array of strings. If your data looks like the first example,
HOSTNAME1
HOSTNAME2
[etc...]
you can read it into an array by simply using Get-Content, as follows (note that I changed the extension to .txt to reflect the actual format of the data):
$computers = Get-Content "\\Path\hostnames.txt"
If your data looks like the second example,
"HOSTNAME1","HOSTNAME2",[etc...]
you can read it into array like this:
$computers = (Get-Content "\\Path\hostnames.txt") -split ','
On the other hand, it appears that you are using Export-Csv correctly: You're exporting a bunch of objects with the same properties into a flat file, which is the correct usage of the term "CSV".