Create Table showing Foldername and Count of files - powershell

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

Related

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
}

Powershell - How To Filter Output

I need assistance in filtering what is going in to my output.
Here is my code:
$FolderPath = Get-ChildItem -Directory -Path "Z:\D482\F11\SECURE" -Recurse -Force
$Output = #()
ForEach ($Folder in $FolderPath) {
$Acl = Get-Acl -Path $Folder.FullName
ForEach ($Access in $Acl.Access) {
$Properties = [ordered]#{'Folder Name'=$Folder.FullName;'Group/User'=$Access.IdentityReference }
$Output += New-Object -TypeName PSObject -Property $Properties
}
}
$Output | ConvertTo-Csv | Out-File C:\Temp\Secured.txt
My output looks like this:
#TYPE System.Management.Automation.PSCustomObject
"Folder Name","Group/User"
"Z:\D482\F11\SECURE\QA\CDM\To_Load\Duals\Load_Completed","S-1-5-21-1275210071-879983540-1801674531-105509"
"Z:\D482\F11\SECURE\QA\Files\CDM\To_Load\Duals\Load_Completed","S-1-5-21-1275210071-879983540-1801674531-121910"
"Z\D482\F11\SECURE\QA\D482\To_Load\Duals\Load_Completed","DOMAIN\CXL3708"
In my output, I only want lines that contain our domain name ( as illustrated by the line with DOMAIN in it.
I have not been successful - either I get nothing, or I get error messages in the console.
here is one way to do the job ... [grin]
what it does ...
sets the constants
builds a parameter splat for the Get-ChildItem call
grabs the dirs from the source path
iterates thru the dir list
gets the acl list for the current dir & filters for those that start with the required domain name
note that i don't have a .IdentityReference property in my ACL, so i used .Owner instead.
iterates thru those acl items
builds a [PSCustomObject] for the current acl
sends that out to the $AccessInfo collection
displays the content of the above on screen
saves the collection to a csv file
the code ...
$SourcePath = $env:TEMP
$DomainName = $env:USERDOMAIN
$ReportFile = "SO_Erich_Powershell - How To Filter Output.csv"
$FullReportFile = Join-Path -Path $env:TEMP -ChildPath $ReportFile
$GCI_Params = #{
LiteralPath = $SourcePath
Directory = $True
Force = $True
Recurse = $True
ErrorAction = 'SilentlyContinue'
}
$DirList = Get-ChildItem #GCI_Params
$AccessInfo = foreach ($DL_Item in $DirList)
{
$AclList = Get-Acl -LiteralPath $DL_Item.FullName |
Where-Object {
$_.Owner -match "^$DomainName"
}
foreach ($AL_Item in $AclList)
{
[PSCustomObject]#{
DirName = $DL_Item.FullName
# my single system has no ".IdentityReference" property
# so i used ".Owner"
GroupOrUser = $AL_Item.Owner
}
}
}
# display the data
$AccessInfo
# send to a csv file
$AccessInfo |
Export-Csv -LiteralPath $FullReportFile -NoTypeInformation
truncated screen output ...
DirName GroupOrUser
------- -----------
C:\Temp\1 MySysName\AnotherUserName
C:\Temp\2 MySysName\AnotherUserName
C:\Temp\3 MySysName\AnotherUserName
[*snip ...*]
C:\Temp\vscode-update-system-x64 MySysName\MyUserName
C:\Temp\WPF MySysName\MyUserName
C:\Temp\mbam\qt-jl-icons MySysName\MyUserName
truncated csv file content ...
"DirName","GroupOrUser"
"C:\Temp\1","MySysName\AnotherUserName"
"C:\Temp\2","MySysName\AnotherUserName"
[*snip ...*]
"C:\Temp\WPF","MySysName\MyUserName"
"C:\Temp\mbam\qt-jl-icons","MySysName\MyUserName"

How do I process multiple CSV files in Powershell and give them output names?

So I'm trying to process CSV files, then giving the output new name. I can do it with one file by explicitly specifying the file name. But is there a way / wildcard I can use to make the script to process multiple files at the same time? Let's just say I want to process anything with .csv as an extension. Here's my script that's used to process a specific file
$objs =#();
$output = Import-csv -Path D:\TEP\FilesProcessing\Test\file1.csv | ForEach {
$Object = New-Object PSObject -Property #{
Time = $_.READ_DTTM
Value = $_.{VALUE(KWH)}
Tag = [String]::Concat($_.SUBSTATION,'_',$_.CIRCUITNAME,'_',$_.PHASE,'_',$_.METERID,'_KWH')
}
$objs += $Object;
}
$objs
$objs | Export-CSv -NoTypeInformation D:\TEP\FilesProcessing\Test\file1_out.csv
You can combine Get-ChildItem and Import-Csv.
Here's an example that specifies different input and output directories to avoid name collisions:
$inputPath = "D:\TEP\FilesProcessing\Test"
$outputPath = "D:\TEP\FilesProcessing\Output"
Get-ChildItem (Join-Path $inputPath "*.csv") | ForEach-Object {
$outputFilename = Join-Path $outputPath $_.Name
Import-Csv $_.FullName | ForEach-Object {
New-Object PSObject -Property #{
"Time" = $_.READ_DTTM
"Value" = $_.{VALUE(KWH)}
"Tag" = "{0}_{1}_{2}_{3}_KWH" -f $_.SUBSTATION,$_.CIRCUITNAME,$_.PHASE,$_.METERID
}
} | Export-Csv $outputFilename -NoTypeInformation
}
Note that there's no need for creating an array and repeatedly appending it. Just output the custom objects you want and export afterwards.
Use the Get-Childitem and cut out all the unnecessary intermediate variables so that you code it in a more Powershell type way. Something like this:
Get-CHhilditems 'D:\TEP\FilesProcessing\Test\*.csv' | % {
Import-csv $_.FullName | % {
New-Object PSObject -Property #{
Time = $_.READ_DTTM
Value = $_.{VALUE(KWH)}
Tag = '{0}_{1}_{2}_{3}_KWH' -f $_.SUBSTATION, $_.CIRCUITNAME, $_.PHASE, $_.METERID
}
} | Export-CSv ($_.FullName -replace '\.csv', '_out.csv') -NoTypeInformation
}
The Get-ChildItem is very useful for situations like this.
You can add wildcards directly into the path:
Get-ChildItem -Path D:\TEP\FilesProcessing\Test\*.csv
You can recurse a path and use the provider to filter files:
Get-ChildItem -Path D:\TEP\FilesProcessing\Test\ -recurse -include *.csv
This should get you what you need.
$Props = #{
Time = [datetime]::Parse($_.READ_DTTM)
Value = $_.{VALUE(KWH)}
Tag = $_.SUBSTATION,$_.CIRCUITNAME,$_.PHASE,$_.METERID,'KWH' -join "_"
}
$data = Get-ChildItem -Path D:\TEP\FilesProcessing\Test\*.csv | Foreach-Object {Import-CSV -Path $_.FullName}
$data | Select-Object -Property $Props | Export-CSv -NoTypeInformation D:\TEP\FilesProcessing\Test\file1_out.csv
Also when using Powershell avoid doing these things:
$objs =#();
$objs += $Object;

Append Member/Column to PSObject

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

File and folder count

There is a folder on the remote server which has various subfolders in it. It is completely nested. I would like to:
Prepare an HTML report which contains folder name.
For every folder it should also record the file count.
The code needs to append the HTML file which is already created.
Columns required: Folder name, Folder Path, File Count
Below is the code snippet which is part of my main script. I am fairly new to PowerShell.
Can some one please help?
$server_dir = "D:\Data\Inbox"
$does_dir_e = (Test-Path $server_dir)
if($does_dir_e)
{
$fso = New-Object -com "Scripting.FileSystemObject"
$f = $fso.GetFolder($server_dir)
foreach($folder in $f.subfolders)
{
$fcount = $((Get-ChildItem $folder.Path).count)
$fname = $folder.name | Convertto-HTML -Fragment >> C:\Temp\Server.html
}
}
You don't actually say what isn't working for you, but the following script should get you started.
The outer loop recurses through the folders (PSIsContainer) means it is a folder.
The inner loop counts the number of files in each folder using measure-object, we filter out folders from this count to give us just the file count.
$path = "D:\Data\Inbox"
# Enumerate the given path recursively
Get-ChildItem -Path $path -Recurse | Where-Object {$_.PSIsContainer} | %{
# Add a user-defined custom member with a value of the filecount this
# time not recursively (using measure object)
$_ | add-member -membertype noteproperty -name FileCount -value (Get-ChildItem -Path $_.Fullname |
Where-Object {!$_.PSIsContainer} |
Measure-Object).Count
# Output the required values
$_ | select Name, FullName, FileCount | ConvertTo-Html -Fragment
}
Is this what you want? I haven't used the HTML cmdlet before, so be aware it's ugly : )
$server_dir = 'D:\Data\Inbox'
if(Test-Path $server_dir)
{
$folders = Get-ChildItem $server_dir -Recurse | where {$_.PSIsContainer}
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | where {!$_.PSIsContainer} | Measure-Object | Select-Object -Expand Count
$obj = New-Object psobject -Property #{FolderName = $fname; FolderPath = $fpath; FileCount = $fcount}
$output += $obj
}
#Output to HTML
$output | ConvertTo-Html -Fragment >> 'C:\Temp\Server.html'
}