How do I get directory depth in PowerShell 3.0? - powershell

I need to find out how far down the directory structure inside a working directory goes. If the layout is something like
Books\
Email\
Notes\
Note 1.txt
Note 2.txt
HW.docx
then it should return 1, because the deepest items are 1 level below. But if it looks like
Books\
Photos\
Hello.c
then it should return 0, because there is nothing deeper than the first level.

Something like this should do the trick in V3:
Get-ChildItem . -Recurse -Name | Foreach {($_.ToCharArray() |
Where {$_ -eq '\'} | Measure).Count} | Measure -Maximum | Foreach Maximum

It's not as pretty, and arguably not as "Posh" as Keith's, but I suspect it might scale better.
$depth_ht = #{}
(cmd /c dir /ad /s) -replace '[^\\]','' |
foreach {$depth_ht[$_]++}
$max_depth =
$depth_ht.keys |
sort length |
select -last 1 |
select -ExpandProperty length
$root_depth =
($PWD -replace '[^\\]','').length
($max_depth -$root_depth)

Related

Is there a way to display the latest file of multiple paths with information in a table format?

I check every day, whether a CSV-File has been exported to a specific folder (path). At the moment there are 14 different paths with 14 different files to check. The files are being stored in the folder and are not deleted. So i have to differ between a lot of files with "lastwritetime". I would like a code to display the results in table format. I would be happy with something like this:
Name LastWriteTime Length
ExportCSV1 21.09.2022 00:50 185
ExportCSV2 21.09.2022 00:51 155
My code looks like this:
$Paths = #('Path1', 'Path2', 'Path3', 'Path4', 'Path5', 'Path6', 'Path7', 'Path8', 'Path9', 'Path10', 'Path11', 'Path12', 'Path13', 'Path13')
foreach ($Path in $Paths){
Get-ChildItem $path | Where-Object {$_.LastWriteTime}|
select -last 1
Write-host $Path
}
pause
This way i want to make sure, that the files are being sent each day.
I get the results that i want, but it is not easy to look at the results individually.
I am new to powershell and would very much appreciate your help. Thank you in advance.
Continuing from my comments, here is how you could do this:
$Paths = #('Path1', 'Path2', 'Path3', 'Path4', 'Path5', 'Path6', 'Path7', 'Path8', 'Path9', 'Path10', 'Path11', 'Path12', 'Path13', 'Path13')
$Paths | ForEach-Object {
Get-ChildItem $_ | Where-Object {$_.LastWriteTime} | Select-Object -Last 1
} | Format-Table -Property Name, LastWriteTime, Length
If you want to keep using foreach() instead, you have to wrap it in a scriptblock {…} to be able to chain everything to Format-Table:
. {
foreach ($Path in $Paths){
Get-ChildItem $path | Where-Object {$_.LastWriteTime} | Select-Object -Last 1
}
} | Format-Table -Property Name, LastWriteTime, Length
Here the . operator is used to run the scriptblock immediately, without creating a new scope. If you want to create a new scope (e. g. to define temporary variables that exist only within the scriptblock), you could use the call operator & instead.

PowerShell: Select custom expression and expand in one operation

I can do something like
ls . `
| select #{ Name="Dir"; Expression = { $_ | Split-Path } } `
| select -ExpandProperty Dir
to select a custom expression (in this case $_ | Split-Path) into a simple array of values.
Is there a way to merge the two select statements into one, that still yields equivalent results?
I think the OP is looking for ForEach-Object (MSDN), which performs an operation against each item in a collection of input objects. It's similar to the map or Select operation in other languages.
So instead of:
ls . `
| select #{ Name="Dir"; Expression = { $_ | Split-Path } } `
| select -ExpandProperty Dir
You want:
ls . `
| ForEach-Object { $_ | Split-Path }
You can also replace ForEach-Object with %. But it's less readable and less searchable for new PowerShell users:
ls . `
| % { $_ | Split-Path }
What are you trying to do? You could get the same results with just ls/Get-ChildItem and Split-Path. Not sure if you need the Microsoft.PowerShell.Core\FileSystem:: part.
PS C:\Path\To\Dir> Get-ChildItem | Split-Path
Microsoft.PowerShell.Core\FileSystem::C:\Path\To\Dir
Microsoft.PowerShell.Core\FileSystem::C:\Path\To\Dir
Microsoft.PowerShell.Core\FileSystem::C:\Path\To\Dir
PS C:\Path\To\Dir> (Get-ChildItem).FullName | Split-Path
C:\Path\To\Dir
C:\Path\To\Dir
C:\Path\To\Dir
Edit
Assuming nuget list <my-package> outputs a string array, you can use -replace and regex ($ to match string end) to manipulate it and get an array of results.
$((nuget list <my-package>).Replace(" ",",") -replace "$",".nupkg")

Exclude index in powershell

I have a very simple requirement of removing couple of lines in a file. I found little help on the net , where we can make use of Index. Suppose i want to select 5th line i use
Get-Content file.txt | Select -Index 4
Similarly, what if i dont need the 5th and 6th line? How would the statement change?
Get-Content file.txt | Select -Index -ne 4
I tried using -ne between -Index and the number. It did not work. neither did "!=".
Also the below code gives no error but not the desired output
$tmp = $test | where {$_.Index -ne 4,5 }
Pipeline elements does not have Index auto-property, but you can add it, if you wish:
function Add-Index {
begin {
$i=-1
}
process {
Add-Member Index (++$i) -InputObject $_ -PassThru
}
}
Then you can apply filtering by Index:
Get-Content file.txt | Add-Index | Where-Object Index -notin 4,5
Don't know about the Index property or parameter, but you can also achieve it like this :
$count = 0
$exclude = 4, 5
Get-Content "G:\input\sqlite.txt" | % {
if($exclude -notcontains $count){ $_ }
$count++
}
EDIT :
The ReadCount property holds the information you need :)
$exclude = 0, 1
Get-Content "G:\input\sqlite.txt" | Where-Object { $_.ReadCount -NotIn $exclude }
WARNING : as pointed by #PetSerAl and #Matt, ReadCount starts at 1 and not 0 like arrays
Try this:
get-content file.txt | select -index (0..3 + 5..10000)
It's a bit of a hack, but it works. Downside is that building the range takes some time. Also, adjust the 10000 to make sure you get the whole file.
Convert this as an array and use RemoveRange method(ind index, int count)
[System.Collections.ArrayList]$text = gc C:\file.txt
$text.RemoveRange(4,1)
$text

Count number of files in each subfolder, ignoring files with certain name

Consider the following directory tree
ROOT
BAR001
foo_1.txt
foo_2.txt
foo_ignore_this_1.txt
BAR001_a
foo_3.txt
foo_4.txt
foo_ignore_this_2.txt
foo_ignore_this_3.txt
BAR001_b
foo_5.txt
foo_ignore_this_4.txt
BAR002
baz_1.txt
baz_ignore_this_1.txt
BAR002_a
baz_2.txt
baz_ignore_this_2.txt
BAR002_b
baz_3.txt
baz_4.txt
baz_5.txt
baz_ignore_this_3.txt
BAR002_c
baz_ignore_this_4.txt
BAR003
lor_1.txt
The structure will always be like this, so no deeper subfolders. I'm working on a script to count the number of files:
for each BARXXX folder
for each BARXXX_Y folder
textfiles with "ignore_this" in the name, should be ignored in the count
For the example above, this would result into:
Folder Filecount
---------------------
BAR001 2
BAR001_a 2
BAR001_b 1
BAR002 1
BAR002_a 1
BAR002_b 3
BAR002_c 0
BAR003 1
I now have:
Function Filecount {
param(
[string]$dir
)
$childs = Get-ChildItem $dir | where {$_.Attributes -eq 'Directory'}
Foreach ($childs in $child) {
Write-Host (Get-ChildItem $dir | Measure-Object).Count;
}
}
Filecount -dir "C:\ROOT"
(Not ready yet but building) This however, does not work. $child seems to be empty. Please tell me what I'm doing wrong.
Well, to start, you're running ForEach ($childs in $child), this syntax is backwards, so that will cause you some issues! If you swap it, so that you're running:
ForEach ($child in $childs)
You'll get the following output:
>2
>2
>1
>1
>1
>3
>0
Alright, I'm back now with the completed answer. For one, instead of using Write-Out, I'm using a PowerShell custom object to let PowerShell do the hard work for me. I'm setting FolderName equal to the $child.BaseName, and then running a GCI on the $Child.FullName to get the file count. I've added an extra parameter called $ignoreme, that should have an asterisk value for the values you want to ignore.
Here's the complete answer now. Keep in mind that my file structure was a bit different than yours, so my file count is different at the bottom as well.
Function Filecount {
param(
[string]$dir="C:\TEMP\Example",
[string]$ignoreme = "*_*"
)
$childs = Get-ChildItem $dir | where {$_.Attributes -eq 'Directory'}
Foreach ($child in $childs) {
[pscustomobject]#{FolderName=$child.Name;ItemCount=(Get-ChildItem $child.FullName | ? Name -notlike $ignoreme | Measure-Object).Count}
}
}
>Filecount | ft -AutoSize
>FolderName ItemCount
>---------- ---------
>BAR001 2
>BAR001_A 1
>BAR001_b 2
>BAR001_C 0
>BAR002 0
>BAR003 0
If you're using PowerShell v 2.0, use this method instead.
Function Filecount {
param(
[string]$dir="C:\TEMP\Example",
[string]$ignoreme = "*_*"
)
$childs = Get-ChildItem $dir | where {$_.Attributes -eq 'Directory'}
Foreach ($child in $childs) {
$ObjectProperties = #{
FolderName=$child.Name
ItemCount=(Get-ChildItem $child.FullName | ? Name -notlike $ignoreme | Measure-Object).Count}
New-Object PSObject -Property $ObjectProperties
}
}
I like that way of creating an object 1RedOne, haven't seen that before, thanks.
We can improve the performance of the code in a few of ways. By using the Filter Left principle, which states that the provider for any cmdlet is inherently more efficient than running things through PowerShell, by performing fewer loops and by removing an unnecessary step:
Function Filecount
{
param
(
[string]$dir = ".",
[parameter(mandatory=$true)]
[string]$ignoreme
)
Get-ChildItem -Recurse -Directory -Path $dir | ForEach-Object `
{
[pscustomobject]#{FolderName=$_.Name;ItemCount=(Get-ChildItem -Recurse -Exclude "*$ignoreme*" -Path $_.FullName).count}
}
}
So, firstly we can use the -Directory switch of Get-Childitem in the top-level directory (I know this is available in v3.0 and above, not sure about v2.0).
Then we can pipe the output of this directly in to the next loop, without storing it first.
Then we can replace another Where-Object with a provider -Exclude.
Finally, we can remove the Measure-Object as a simple count of the array will do:
Filecount "ROOT" "ignore_this" | ft -a
FolderName ItemCount
---------- ---------
BAR001 2
BAR001_a 2
BAR001_b 1
BAR002 1
BAR002_a 1
BAR002_b 3
BAR002_c 0
BAR003 1
Cheers Folks!

How do I get filename and line count per file using powershell

I have the following powershell script to count lines per file in a given directory:
dir -Include *.csv -Recurse | foreach{get-content $_ | measure-object -line}
This is giving me the following output:
Lines Words Characters Property
----- ----- ---------- --------
27
90
11
95
449
...
The counts-per-file is fine (I don't require words, characters, or property), but I don't know what filename the count is for.
The ideal output would be something like:
Filename Lines
-------- -----
Filename1.txt 27
Filename1.txt 90
Filename1.txt 11
Filename1.txt 95
Filename1.txt 449
...
How do I add the filename to the output?
try this:
dir -Include *.csv -Recurse |
% { $_ | select name, #{n="lines";e={
get-content $_ |
measure-object -line |
select -expa lines }
}
} | ft -AutoSize
I can offer another solution :
Get-ChildItem $testPath | % {
$_ | Select-Object -Property 'Name', #{
label = 'Lines'; expression = {
($_ | Get-Content).Length
}
}
}
I operate on the. TXT file, the return value is like this ↓
Name Lines
---- ----
1.txt 1
2.txt 2
3.txt 3
4.txt 4
5.txt 5
6.txt 6
7.txt 7
8.txt 8
9.txt 9
The reason why I want to sort like this is that I am rewriting a UNIX shell command (from The Pragmatic Programmer: Your Journey to Mastery on page 145).
The purpose of this command is to find out the five files with the largest number of lines.
At present, my progress is the above content,i'm close to success.
However, this command is far more complicated than the UNIX shell command!
I believe there should be a simpler way, I'm trying to find it.
find . -type f | xargs wc -l | sort -n | tail -5
I have used the following script that gives me lines in files of all sub directories in folder c:\temp\A. The output is in lines1.txt file. I have applied a filer to choose only file types of ".TXT".
Get-ChildItem c:\temp\A -recurse | where {$_.extension -eq ".txt"} | % {
$_ | Select-Object -Property 'Name', #{
label = 'Lines'; expression = {
($_ | Get-Content).Length
}
}
} | out-file C:\temp\lines1.txt