How to format PowerShell results from ForEach - powershell

I am trying to get complete a request of amount of files in a directory and then the total size of that directory. I've come up with this:
Get-Content -Path C:\Users\$USERNAME%\Documents\list.txt |
Foreach-Object {
cd $_
Write-Host $_
(Get-ChildItem -Recurse -File | Measure-Object).Count
(ls -r|measure -sum Length).Sum
}
The txt file has contents such as:
\\directory\on\network\1
\\directory\on\network\also
Ultimately I need this in a spreadsheet, but I am failing with formatting. As is, it outputs straight to powershell, so with thousands of directories this isn't ideal. I've tried exporting to CSV but it overwrites the CSV with each result, and when I tried setting the function equal to a variable array and then exporting that, it simply output a blank file.
Any assistance with this is appreciated.

In order to export to CSV you will need an object with properties. Your code generates a few values without any structure. Surely the % in your sample code is a typo, it definitely doesn't belong there. It is generally considered bad practice to use aliases in scripts, however you should, at a minimum, keep it consistent. One line you use Get-ChildItem/Measure-Object and the next use ls/measure. Regardless you don't show your export, so it's hard to help with what we can't see. You also don't need to CD into the directory, it seems it would only slow the script down if anything.
The easiest way I know to create an object is to use the [PSCustomObject] type accelerator.
$infile = "C:\Users\$USERNAME\Documents\list.txt"
$outfile = "C:\some\path\to.csv"
Get-Content -Path $infile |
Foreach-Object {
Write-Host Processing $_
[PSCustomObject]#{
Path = $_
Total = (Get-ChildItem $_ -Recurse -File | Measure-Object).Count
Size = (Get-ChildItem $_ -Recurse -File | Measure-Object -sum Length).Sum
}
} | Export-Csv $outfile -NoTypeInformation
Edit
We should've ran the Get-Childitem call once and then pulled the info out. The first option is in "pipeline" mode can save on memory usage but might be slower. The second puts it all in memory first so it can be much quicker if it's not too large.
Get-Content -Path $infile |
Foreach-Object {
Write-Host Processing $_
$files = Get-ChildItem $_ -Recurse -File | Measure-Object -sum Length
[PSCustomObject]#{
Path = $_
Total = $files.Count
Size = $files.Sum
}
} | Export-Csv $outfile -NoTypeInformation
or
$results = foreach($folder in Get-Content -Path $infile)
{
Write-Host Processing $folder
$files = Get-ChildItem $folder -Recurse -File | Measure-Object -sum Length
[PSCustomObject]#{
Path = $folder
Total = $files.Count
Size = $files.Sum
}
}
$results | Export-Csv $outfile -NoTypeInformation

The -append flag in Export-Csv allows you to add to an existing file rather than overwriting.

Related

List all files with size using powershell char limit issue

I have an applications folder that have more than 10 applications in. I want to list all files (including sub-folders) with directory and size info and save it under each application folder.
Here is the script (it is in C:\Users\xxxxxx\Desktop\apps)
$FolderList = Get-ChildItem -Directory
foreach ($folder in $FolderList)
{
$thisPath = Get-Location
Get-ChildItem -File -Filter * -Path $folder -Recurse |
Sort-Object -Property Length -Descending|
Select-Object -Property FullName, Length, #{l='Length (MB)';e={$_.Length/1MB}} |
Format-Table -AutoSize |
Out-File $thisPath\fileList_$folder.txt
}
Output:
FullName - Length - Length (MB)
C:\Users\xxxxxx\Desktop\apps\3\VSCodeUserSetup-x64-1.62.2.exe 79944464 76.2409820556641
C:\Users\xxxxxx\Desktop\apps\3\son.zip 18745870 17.8774547576904
It does what I want but in some of the outputs where the path is too long it didn't write the length or even full path.
FullName
C:\Users\xxxxxx\Desktop\apps\3\xxxxxxxxxxx/xxxxxx/xxxxxxxxxxxxxx/xxxxxxxxxxx/VSCodeUserSetu...
C:\Users\xxxxxx\Desktop\apps\3\son.zip
As I searched I saw that there is a char limit. How can I overcome this issue?
The -Path parameter is defined as a string array, but you are feeding it a DirectoryInfo object.
The second part of your question is about truncation, which happens because you use Format-Table -AutoSize on the data.
Format-* cmdlets are designed to display output on the console window only which has a limited width and all items longer are truncated.
Rule of Thumb: never use Format-* cmdlets when you need to process the data later.
Instead of saving a formatted table to a text file, I would suggest saving the (object) info as structured CSV file which makes working with the data later MUCH easier and without truncation. (you can for instance open it in Excel)
# just get an array of Full pathnames
$FolderList = (Get-ChildItem -Directory).Name #instead of Fullname
foreach ($folder in $FolderList) {
# create the path for the output
$fileOut = Join-Path -Path $folder -ChildPath ('filelist_{0}.csv' -f $folder)
Get-ChildItem -File -Path $folder -Recurse |
Sort-Object -Property Length -Descending |
Select-Object -Property FullName, Length, #{l='Length (MB)';e={$_.Length/1MB}} |
Export-Csv -Path $fileOut -NoTypeInformation -UseCulture
}
I added switch -UseCulture to the Export-Csv cmdlet so you can afterwards simply double-click the csv file to open it in your Excel

Find share sizes across multiple servers -powershell

I have about 1600 old home drives that I need to capture the folder sizes on. They are spread across 12 NAS Appliances and I need to get the size of each folder. I see this asked a lot but they are always asking for all the shares on a single server.
I have used simple one-liners like the the one below which work great.
Get-ChildItem \\Server1\Users\User1 -file -recurse | Measure-Object -Sum Length
How can I feed in a txt or csv from multiple locations?
Like
\\Server1\Users\User1
\\Server2\Users\User1
\\Server3\Users\User1
\\Server4\Users\User1
\\Server5\Users\User1
\\Server6\Users\User1
\\Server7\Users\User1
etc...
Exporting to a csv would be nice as well.
thanks!!
You can use Get-Content to read your text file line-by-line:
Get-Content .\path\to\file.txt
Next up we need to repeat your one-liner for each line:
Get-Content .\path\to\file.txt |ForEach-Object {
Get-ChildItem $_ -File -Recurse | Measure-Object -Sum Length
}
This will give us 1600 individual measurements - but now we won't know which paths they belong to...
To track which measurement belongs to which user directory, let's create a new object containing both all the information instead:
Get-Content .\path\to\file.txt |ForEach-Object {
$Measurement = Get-ChildItem $_ -File -Recurse | Measure-Object -Sum Length
[pscustomobject]#{
Path = $_
FileCount = $Measurement.Count
TotalSize = $Measurement.Sum
}
}
Finally, we can export to CSV using Export-Csv:
Get-Content .\path\to\file.txt |ForEach-Object {
$Measurement = Get-ChildItem $_ -File -Recurse | Measure-Object -Sum Length
[pscustomobject]#{
Path = $_
FileCount = $Measurement.Count
TotalSize = $Measurement.Sum
}
} |Export-Csv .\path\to\output.csv -NoTypeInformation

Check if files exist using Powershell

I have a Powershell script which I've cobbled together. It uses an external file as a lookup then checks the LastWriteTime of those files.
This was created as a checking procedure. To ensure a set of files had been updated each day.
However, I've noticed that if the files don't exist at run time, they don't show in the list at all. So there's potential for these to be missed.
As well as checking the LastWriteDate, is there a way this can be altered to highlight in some way if any of the files don't exist?
Either a new column saying Exists Y/N?
Or even a total row count VS expected row count?
This is what I've got so far...
#Filelist - This is a simple txt file with various filepaths entered
$filelist = Get-Content "H:\PowerShell\Files_Location_List.txt"
$results = foreach ($file in $filelist) {
Get-Item $file | select -Property fullname, LastWriteTime #| Measure-Object
}
$results | Export-Csv 'H:\PowerShell\File_Modified_Dates.csv' -NoTypeInformation #| Measure-Object
The contents of Files_Location_List.txt is very simple...
\server\folder\file1.csv
\server\folder\file2.csv
\server\folder\file3.csv
etc
you can try using Test-Path
if(Test-Path -Path <file_path>) {
# do stuff
}
You can also use -ErrorAction SilentlyContinue on Get-Item to either get a FileInfo object if the file exists or $null if not:
# Filelist - This is a simple txt file with various filepaths entered
$result = Get-Content "H:\PowerShell\Files_Location_List.txt" | ForEach-Object {
# try and get the FileInfo object for this path. Discard errors
$file = Get-Item -Path $_ -ErrorAction SilentlyContinue
if ($file) {
$file | Select-Object -Property FullName, LastWriteTime, #{Name = 'Exists'; Expression = {$true}}
}
else {
[PsCustomObject]#{
FullName = $_
LastWriteTime = $null
Exists = $false
}
}
}
$result | Export-Csv 'H:\PowerShell\File_Modified_Dates.csv' -NoTypeInformation

Powershell search directory for code files with text matching input a txt file

Data mapping project, in house system to new vendor system. First step is find all the occurrences of current database field names (or column names to be precise) in the C# .cs source files. Trying to use Powershell. Have recently created PS searches with Get-ChildItem and Select-String that work well but the search string array was small and easily hard coded inline. But the application being ported has a couple hundred column names and significant amounts of code. So armed with a text file of all the column names Pipleline would seem like a god tool to create a the basic cross ref for further analysis. However, I was not able to get the Pipeline to work with an external variable anyplace other than first step. Trying using -PipelineVariable, $_. and global variable. Did not find anything specific after lots of searching. P.S. This is my first question to StackoOverflow, be kind please.
Here is what I hoped would work but do dice so far.
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr = [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force |
Select-String $s | Select-Object Path, LineNumber, line | Export-csv $outputfile
}
Did find that this will print the list one time but not twice. In fact it seems using the variable in this way results in processing simply skipping any further pipeline steps.
foreach ($s in $arr) {Write-Host $s | Write $s}
If it isn't possible to do this in Powershell easily my fallback is to do with C# although would much rather get the level up with PowerShell if anyone can point me to the correct understanding of how to do things in the Pipepline, or alternatively construct an equivalent function. Seems like such a natural fit for Powershell.
Thanks.
You're calling Export-csv $outputfile in a loop, which rewrites the whole file in every iteration, so that only the last iteration's output will end up in the file.
While you could use -Append to iteratively append to the output file, it is worth aking a step back: Select-String can accept an array of patterns, causing a line that matches any of them to be considered a match.
Therefore, your code can be simplified as follows:
$inputFile = 'C:\DataColumnsNames.txt'
$outputFile = 'C:\DataColumnsUsages.txt'
Get-ChildItem C:\ProjectFolder -Filter *.cs -Recurse -Force -ea SilentlyContinue |
Select-String -Pattern (Get-Content $inputFile) |
Select-Object Path, LineNumber, line |
Export-csv $outputfile
-Pattern (Get-Content $inputFile) passes the lines of input file $inputFile as an array of patterns to match.
By default, these lines are interpreted as regexes (regular expressions); to ensure that they're treated as literals, add -SimpleMatch to the Select-String call.
This answer to a follow-up question shows how to include the specific pattern among the multiple ones passed to -Pattern that matched on each line in the output.
I think you want to append each occurrence to the csv file. And you need to get the content of the file. Try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | Export-csv -Append -Path "$outputfile"
}
}
-Append was not introduced before powershell v3.0 (Windows 8) then try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | ConvertTo-CSV -NoTypeInformation | Select-Object -Skip 1 | Out-File -Append -Path "$outputfile"
}
}

Using Powershell to export to CSV with columns

I am trying to do a simple script that pulls in the name of the file and the contents of said text file into a CSV file. I am able to pull in all of the information well enough but it's not splitting up into different columns in the CSV file. When I open up the CSV file in excel everything is in the first column, and I need the two bits of information separated into separate columns. So far my working code is as follows:
$Data = Get-ChildItem -Path c:path -Recurse -Filter *.txt |
where {$_.lastwritetime -gt(Get-Date).addDays`enter code here`(-25)}
$outfile = "c:path\test.csv"
rm $outfile
foreach ($info in $Data) {
$content = Get-Content $info.FullName
echo "$($info.BaseName) , $content" >> $outfile
}
I figured out how to seperate the information by rows but I need it by columns. I'm new to powershell and can't seem to get past this little speed bump. Any input would be greatly appreciated. Thanks in advance!
Output:
Itm# , TextContent
Itm2 , NextTextContent
What I need:
Itm# | Text Content |
Itm2 | NextTextContent |
Except for a few syntactical errors your code appears to be working as expected. I worry if you are having issues in Excel with you text import. I touched up your code a bit but it is functionally the same as what you had.
$Data = Get-ChildItem -Path "C:\temp" -Recurse -Filter *.txt |
Where-Object {$_.LastWriteTime -gt (Get-Date).addDays(-25)}
$outfile = "C:\temp\test.csv"
If(Test-Path $outfile){Remove-Item $outfile -Force}
foreach ($info in $Data) {
$content = Get-Content $info.FullName
"$($info.BaseName) , $content" | Add-Content $outfile
}
I don't know what version of Excel you have but look for the text import wizard.
Do you mean something like this?
Get-ChildItem -Path C:\path -Recurse -Filter *.txt |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-25) } | ForEach-Object {
New-Object PSObject -Property #{
"Itm#" = $_.FullName
"TextContent" = Get-Content $_.FullName
} | Select-Object Itm#,TextContent
} | Export-Csv List.csv -NoTypeInformation
Excel will treat the data in csv files which are delimited bij the ; as a single columns.
I always use the -delimiter switch on export-csv or convertto-csv to set this as a delimiter.