I have worked on a little project which is to extract some information out of a file server. To perform that projet I have created a script that outputs all the information in a .csv file. The problem is that Powershell eats up all my computer's RAM during the process because there is like hundreds Gb of data to parse.
Hereunder is my script.
$folder = Get-ChildItem -Recurse 'Complete_Path' | select FullName, #{Name="Owner";Expression={(Get-Acl $_.FullName).Owner}}, CreationTime, LastWriteTime, LastAccessTime, PSIsContainer | sort FullName
$output = #()
$folder | foreach {
$type =
if ($_.PSIsContainer -eq "True") {
Write-Output "Folder"
}
else {
Write-Output "File"
}
$size =
if ($_.PSIsContainer -eq "True") {
Get-ChildItem -Recurse $_.FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum
}
else {
Get-Item $_.FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum
}
$hash = #{
FullName = $_.FullName
Owner = $_.Owner
CreationTime = $_.CreationTime
LastWriteTime = $_.LastWriteTime
LastAccessTime = $_.LastAccessTime
Type = $type
'Size in MB' = [math]::Round($($size/1Mb),2)
}
$output += New-Object PSObject -Property $hash
}
$output | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime, Type, 'Size in MB' | Export-Csv C:\myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8
Have you guys any idea how can I get the job done faster and less ram consuming? It can take days to get the extraction.
Thank you in advance.
Replace your Powershell array $output=#() with A .Net PSObject list $output = [System.Collections.Generic.List[psobject]]::new() and use the .Add method of that object to add your items.
For small list, you won't notice but using Powershell array and += operator is a big performance sink. Each time you do +=, array is recreated entirely with one more item.
Include Length in your initial Get-ChildItem statement. Later on, you can measure the sum without going through Get-ChildItem again all the time
Pipeline play nice on memory but slower overall. I tend to prefer not using the pipeline when performance become an issue.
Something like that should already be significantly faster
$folder = Get-ChildItem -Recurse "$($env:USERPROFILE)\Downloads" | select FullName, #{Name = "Owner"; Expression = { (Get-Acl $_.FullName).Owner } }, CreationTime, LastWriteTime, LastAccessTime, PSIsContainer, Length | sort FullName
$output = [System.Collections.Generic.List[psobject]]::new()
foreach ($Item in $folder) {
if ($Item.PSIsContainer) {
$Type = 'Folder'
$size = $folder.Where( { $_.FullName -like $item.FullName }).FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum
}
else {
$Type = 'File'
$size = $Item.Length
}
$size = [math]::Round($($size / 1Mb), 2)
$hash = #{
FullName = $Item.FullName
Owner = $Item.Owner
CreationTime = $Item.CreationTime
LastWriteTime = $Item.LastWriteTime
LastAccessTime = $Item.LastAccessTime
Type = $Type
'Size in MB' = $size
}
[void]($output.Add((New-Object PSObject -Property $hash)))
}
$output | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime, Type, 'Size in MB' | Export-Csv C:\myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8
You could still improve on the size calculation so the deepest of folders size is calculated first, then parent folder can grab the value and sum up the children folder instead of recalculating the files
Another thought would be to not do the Get-ACl immediately (I suspect this one is slow to perform ) and get your items, do the rest, then parallelize the Get-ACL so you can get the values on a number of parallel threads and add the the value as you get it to your list.
Think about testing your code on smaller batches and use the Measure-Command to determine where are the slowest operation in your code.
I recommend you taking a look at some more advanced topic on the subject.
Here's a good article to get your started : Slow Code: Top 5 ways to make your Powershell scripts run faster
Is this better with the whole thing in one pipeline?
Get-ChildItem -Recurse |
select FullName, #{Name="Owner";Expression={(Get-Acl $_.FullName).Owner}},
CreationTime, LastWriteTime, LastAccessTime, PSIsContainer | sort FullName |
foreach {
$type =
if ($_.PSIsContainer -eq "True") {
Write-Output "Folder"
}
else {
Write-Output "File"
}
$size =
if ($_.PSIsContainer -eq "True") {
Get-ChildItem -Recurse $_.FullName |
measure -Property Length -Sum -ErrorAction SilentlyContinue |
select -ExpandProperty Sum
}
else {
Get-Item $_.FullName |
measure -Property Length -Sum -ErrorAction SilentlyContinue |
select -ExpandProperty Sum
}
$hash = #{
FullName = $_.FullName
Owner = $_.Owner
CreationTime = $_.CreationTime
LastWriteTime = $_.LastWriteTime
LastAccessTime = $_.LastAccessTime
Type = $type
'Size in MB' = [math]::Round($($size/1Mb),2)
}
New-Object PSObject -Property $hash
} | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime,
Type, 'Size in MB' |
Export-Csv myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8
Related
I ve got a script which get each folder and get name,filescount,size of each folder.
Size with measure-object doesn t work.
My first try using my own object to handle this([pscustomobject]).
May I integrate a command (measure-object) in an object ?
Get-ChildItem d:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue|
ForEach-Object{
[pscustomobject]#{
FullName = $_.Fullname
FileCount = $_.GetFiles().Count
size=measure-object -sum -property length
}
} | sort -Property filecount -Descending
Thks ,)
Unfortunately folders don't actually have a size (Windows is just kind enough to find out for us when we check the properties of it)
So in your script you need to get all child items of the current iterations folder and measure their combined size.
Get-ChildItem d:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue |
ForEach-Object {
[pscustomobject]#{
FullName = $_.Fullname
FileCount = $_.GetFiles().Count
size = (Get-Childitem -Path $_.Fullname -recurse | measure-object -property length -sum).sum
}
} | sort -Property filecount -Descending
You will be using the result from .GetFiles() twice, first for getting the total file count and the second time to get the sum of the file's Length, hence I would advise you to store the result of that method call in a variable for further manipulation.
To get the folder size, you can either use Enumerable.Sum:
Get-ChildItem D:\ -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue | & {
process {
$files = $_.GetFiles()
[pscustomobject]#{
FullName = $_.FullName
FileCount = $files.Count
Size = [Linq.Enumerable]::Sum([int64[]] $files.ForEach('Length'))
}
}
} | Sort-Object -Property Filecount -Descending
Or, if you want to use Measure-Object, the Size property would be:
Size = ($files | Measure-Object Length -Sum).Sum
Lastly, if you want to do a recursive search per folder, you can target the .GetFiles(String, SearchOption) overload using AllDirectories for SearchOption Enum:
$files = $_.GetFiles('*', [IO.SearchOption]::AllDirectories)
I am new to PowerShell scripting. For one of my School project I need to generate a report of the entire D Drive. I need to list all the folders sorted according to the size
Summary of the work I have done so far:
I have installed a PowerShell module from this website
https://www.gngrninja.com/script-ninja/2016/5/24/powershell-calculating-folder-sizes
using Install-Module PSFolderSize
After installing if I run the command Get-FolderSize I'm getting the FolderSize for the path I'm running from. The foldersize is not running for all the folders in the directory.
I am facing difficulty traversing through all the folders.
Expected Output:
+-------------+--------------+--------------+-----------+-----------------------+-----------+
| FolderName | Size(Bytes) | Size(MB) | Size(GB) | FullPath | HostName |
+-------------+--------------+--------------+-----------+-----------------------+-----------+
| Disney | 454545448889 | 433488.32024 | 423.32844 | D:\Videos\Disney | localhost |
| Universal | 25454544884 | 24275.34569 | 23.70639 | D:\Videos\Universal | localhost |
| Fox Studios | 8803063287 | 8395.25536 | 8.19849 | D:\Videos\Fox Studios | localhost |
+-------------+--------------+--------------+-----------+-----------------------+-----------+
Can anyone help me where to start?
I would use other tools (like TreeSize) for creating reports like this because of the speed and long filename problematics.
However you could solve your task with the following powershell command without downloading an other Module.
For each subfolder inside your D:\ you have to receive your required data.
You have to calculate the size of each directory by looking for each file Get-ChildItem -Path $Folder.FullName -Recurse -Force and sum up the lenght of all files.
Here you can use the Measure-Object -Property Length -Sum. Depending on the directory size this task will take some time.
Take a look here if you struggle with long file name issues.
After collection and adding all data to an output variable use Select-Object -Property 'FolderName', 'Size(Bytes)', 'Size(MB)', 'Size(GB)', 'FullPath', 'HostName' for sorting the header order.
The command Sort-Object -Property 'Size(Bytes)', 'FolderName' will sort the output depending on the folder size and name.
For a nice looking output use Format-Table.
[System.String]$Path = 'D:\'
[PSCustomObject[]]$Output = #()
foreach ($Folder in (Get-ChildItem -Path $Path -Directory))
{
[System.Int64]$Size = (Get-ChildItem -Path $Folder.FullName -Recurse -Force | Measure-Object -Property Length -Sum).Sum
[System.Collections.Hashtable]$Hashtable = #{
'FolderName' = $Folder.Name
'Size(Bytes)' = $Size
'Size(MB)' = $Size / 1MB
'Size(GB)' = $Size / 1GB
'FullPath' = $Folder.FullName
'HostName' = $env:COMPUTERNAME
}
$Output += New-Object -TypeName 'PSCustomObject' -Property $Hashtable
}
$Output | `
Select-Object -Property 'FolderName', 'Size(Bytes)', 'Size(MB)', 'Size(GB)', 'FullPath', 'HostName' | `
Sort-Object -Property 'Size(Bytes)', 'FolderName' | `
Format-Table
[System.String]$Path = 'D:\'
[PSCustomObject[]]$Output = #()
foreach ($Folder in (Get-ChildItem -Recurse $Path | Where-Object { $_.PSIsContainer }))
{
[System.Int64]$Size = (Get-ChildItem -Path $Folder.FullName -Recurse -Force | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
[System.Collections.Hashtable]$Hashtable = #{
'FolderName' = $Folder.Name
'Size(Bytes)' = $Size
'Size(MB)' = [Math]::Round($Size / 1MB ,2)
'Size(GB)' = [Math]::Round($Size / 1GB,2)
'FullPath' = $Folder.FullName
'HostName' = $env:COMPUTERNAME
}
$Output += New-Object -TypeName 'PSCustomObject' -Property $Hashtable
}
$Output | `
Select-Object -Property 'FolderName', 'Size(Bytes)', 'Size(MB)', 'Size(GB)', 'FullPath', 'HostName' | `
Sort-Object -Property 'Size(Bytes)' , 'FolderName' -Descending | `
Format-Table
I have a PowerShell script which compares two files in two different folders. If a file with the proper number exists in the first folder then it runs it.
If the file doesn't exist in the first folder then it copies it from the second folder to the first folder and runs it from the first folder.
function Invoke-InstallationOfANewBuild()
{
param (
$ptud = "$($env:USERPROFILE)\Desktop\",
$ptbf = "\\r\P\Al\O\D B\R 017\x64"
)
begin {
$output1 = Get-ChildItem $ptbf -Filter *.exe | Where Name -NotMatch '.*NoDB\.exe$' | % {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort No -Descending | Select -ExpandProperty Name -First 1
$output2 = Get-ChildItem $ptbf -Filter *.exe | Where Name -NotMatch '.*NoDB\.exe$' | % {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
} | Sort No -Descending | Select -ExpandProperty Name -First 1
}
Compare-Object -ReferenceObject $output1 -DifferenceObject $output2
}
process {
if ($LASTEXITCODE = 0)
{
Get-ChildItem $ptud -Filter *.exe | Where Name -NotMatch '.*NoDB\.exe$' | % {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort No -Descending | Select -ExpandProperty Name -First 1 | Foreach { & $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes" }
}
else
{
Get-ChildItem $ptbf -Filter *.exe | Where Name -NotMatch '.*NoDB\.exe$' | % {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort No -Descending | Select -ExpandProperty Name -First 1 | Copy-Item -Destination $ptud | Foreach { & $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes" }
}
}
end { return $LASTEXITCODE }
}
I have a problem in the else block - file copies from the second folder to the first folder but the file execution is not started.
Also I am looking for better solution with if block. I want to say - if operation Compare-Object returns true than start everything in if block, if operation returns false (for example file with such doesn't exist in 1st folder) -than start everything in else block.
For your compare try this:
$compare = Compare-Object -ReferenceObject $A -DifferenceObject $B |
Where-Object { $_.SideIndicator -eq '=>' } |
Measure-Object -Property inputObject
$compare.count -gt 0 # for your if condition
for your copy-object problem, try this:
the Tee-Object wil duplicate the pipeline to a variable
Get-ChildItem $ptbf -Filter *.exe | Where Name -NotMatch '.*NoDB\.exe$' | % {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort No -Descending | Select -ExpandProperty Name -First 1 | Tee-Object -variable Duplicate | Copy-Item -Destination $ptud
$duplicate | Foreach { & $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes" }
I found a script to give me the size of each subfolder within a directory.
The problem is I don't know where to put the export-csv command to get that output in a CSV.
As a bonus I would also like to throw in a lastmodified property to those subfolders if possible. But I realize that may require a different script entirely.
Here is the script:
$startFolder = "C:\Scripts"
$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"
$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
{
$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
}
I've tried adding | export-csv c:\path to the very end, both inside and outside the bracket. I also tried adding it after the script command, ie c:\script.ps1 | export-csv. Each time I get the error that "An empty pipe element is not allowed."
If you want to process the output of a foreach loop with a pipeline you must either collect its output in a variable:
$results = foreach ($i in $colItems) {
...
}
$results | Export-Csv 'C:\path\to\your.csv' -NoType
or run it in an expression (i.e. in parentheses):
(foreach ($i in $colItems) {
...
}) | Export-Csv 'C:\path\to\your.csv' -NoType
However, personally I'd prefer ForEach-Object over foreach loops (for differences between the two see here), because the former does work with pipelines:
Get-ChildItem $startFolder -recurse |
Where-Object {$_.PSIsContainer -eq $True} |
Sort-Object |
ForEach-Object {
...
} |
Export-Csv 'C:\path\to\your.csv' -NoType
With that said, you may not want a loop in the first place. The Export-Csv cmdlet processes a list of objects and writes the properties of the input objects as fields to a CSV file. Since you already have object input you could simply select the properties you want to export:
Get-ChildItem $startFolder -recurse |
Where-Object {$_.PSIsContainer -eq $True} |
Sort-Object |̣
Select-Object FullName, LastWriteTime |
Export-Csv 'C:\path\to\your.csv' -NoType
Custom properties can be added for instance as calculated properties:
Get-ChildItem $startFolder -recurse |
Where-Object {$_.PSIsContainer -eq $True} |
Sort-Object |̣
Select-Object FullName, LastWriteTime, #{n='FolderSize';e={
Get-ChildItem $_.FullName |
Measure-Object -Property Length -Sum |
Select-Object -Expand Sum
}} |
Export-Csv 'C:\path\to\your.csv' -NoType
1)How to create a list of attributes of files with cmdlet get-member and then sort it by last write time?
2)Find total size of files with different extension(for examp total size for all *.html files)
I think the solution for the first task(second task is ok) should be like this(however it doesn't work)
$a=get-childitem . -filter *.html
$n=$a.Length
do{
$isnotsorted=0
for($i=0;$i -lt ($n-1); $i++) {
if ((get-member $a[$i]).LastWriteTime -lt (get-member $a[$i]).LastWRiteTime){
$a[$i],$a[$i+1]=`
$a[$i+1],$a[$i]
$isnotsorted=$i+1
}
}
$n=$isnotsorted
}
until ($n -eq 0)
$a
You don't need to use Get-Member to do this. You can use Sort-Object and Select-Object:
dir C:\ -Force | ? {!$_.PsIsContainer} | Sort LastWriteTime | Select FullName, Attributes
You can use Group-Object and Measure-Object to do this.
((dir D:\Software -Force -Filter *.html | Group Extension).Group | Measure-Object -Sum Length).Sum / 1MB
I'm not sure why you don't want to use Sort-Object -Property LastWriteTime but here is how you would fix your bubble sort code. Remember Get-Member is not the right cmdlet to use to access a properties value.
$a = get-childitem -filter *.html
$n = $a.Length
do {
$isnotsorted = 0
for($i = 0; $i -lt ($n-1); $i++) {
if ( ($a[$i]).LastWriteTime -lt ($a[$i + 1]).LastWRiteTime ) {
$a[$i] , $a[$i+1] = $a[$i+1] , $a[$i]
$isnotsorted = $i + 1
}
}
$n = $isnotsorted
} until ($n -eq 0)
$a
Another thing to note here is that the performance of this algorithm is much worse than just using Sort-Object. My music folder has 1355 files and the above finishes in 83 seconds. Using Sort-Object finishes in 1.7 seconds.
Measure-Command {
get-childitem D:\shares\Music -rec -filter *.m4a | Sort-Object LastWriteTime
}
You don't need Get-Member to display the attributes of files. Just use Get-ChildItem to get the contents of a directory and then pipe them to Sort-Object:
Get-ChildItem -Path $path | Sort-Object -Property 'LastWriteTime'
You can add the -Recurse parameter to Get-ChildItem to list child directories, and add -Force to list files with the Hidden attribute. You can pipe all of this to a Format-* cmdlet if you want to display properties other than those displayed by the standard formatting for files and directories:
Get-ChildItem -Path $path `
| Sort-Object -Property 'LastWriteTime' `
| Format-Table -Property #('Attributes', 'FullName', 'CreationTime')
Get-Member can be used to determine which properties exist on a file or directory object.
You can use Measure-Object with the -Sum switch to add up the Length property of a collection of files:
$htmlFiles = Get-ChildItem -Path $path -Filter '*.html';
$measurement = $htmlFiles | Measure-Object -Property 'Length' -Sum;
$totalHtmlSize = $measurement.Sum;
To generate a table of the total size of each file type you can do something like this:
Get-ChildItem -Path $path `
| Where-Object { $_ -is [IO.FileInfo]; } `
| Group-Object -Property 'Extension' `
| Select-Object `
#{ Name = 'Extension'; Expression = 'Name' }, `
#{ Name = 'TotalSize'; Expression = { `
($_.Group | Measure-Object -Property 'Length' -Sum).Sum } `
} `
| Sort-Object -Property 'Extension';
That retrieves the contents of $path, filters it to only include files, groups the files by the Extension property, projects each group into an object with a property for the extension and a property for the total file size, then sorts the results by extension.