Using Powershell, I want to convert a bunch of absolute paths to relative paths:
cmd /c dir /s /b /a-d | resolve-path -relative | sort-object
However, there are many funny characters in the directories that confuses Resolve-Path that it thought they were wildcard patterns.
How to pipe them to resolve-path's -LiteralPath?
I read that it should be done by ByPropertyName piping and that the value should be put to the "LiteralPath" property of the input object.
But I don't know how. The articles on the web confuse the hell out of me.
Please help and many thanks.
Specify that the pipeline input value should be bound to LiteralPath instead, like this:
cmd /c dir /s /b /a-d|Resolve-Path -LiteralPath {$_} -Relative
Although, if you just want to recurse through the directory tree, gather all files and resolve their relative path, use Get-ChildItem instead - the LiteralPath parameter on built-in cmdlets is always aliased PSPath, which happens to be the name of a property that the providers add to all drive items, making the pipeline-binding work automatically:
Get-ChildItem -File -Recurse |Resolve-Path -Relative
# Explicit binding to LiteralPath no longer required
Related
I'm a bit confused by the -Depth flag for Get-ChildItem. The following works great (finds all files and folders only one deep under "C:\Program Files"):
dir 'C:\Program Files\' -Depth 1
But if I then want to extend it to find only *.txt type files, I cannot find how to do that (following just give weirdly unexpected output where -Depth 1 is ignored and it instead does the equivalent of a -Recurse to all subfolders no matter how deep):
dir 'C:\Program Files\*.txt' -Depth 1
dir 'C:\Program Files\' -Include *.txt -Depth 1
dir 'C:\Program Files\*' -Include *.txt -Depth 1
How do we use -Depth to a specific depth for Get-ChildItem and a required file-pattern?
The behavior you're seeing is a bug in Windows PowerShell, that has since been fixed in PowerShell [Core] 6+ - see this GitHub issue.
Given that Windows PowerShell is no longer actively developed, it is unlikely that the bug will be fixed.
To spell it out, Windows PowerShell ignores -Depth's depth constraint in the following cases:
with -Include or -Exclude
if the (implied) -Path argument contains wildcard characters.
While recursion is still performed, no depth limit is imposed; in effect, -Depth behaves like -Recurse (alone) in these cases.
Workarounds:
For -Include and wildcard-based -Path arguments where the wildcards are limited to the last path component:
Use -Filter instead, as shown in Wasif Hasan's answer.
Caveat: -Filter is usually preferable anyway for its superior performance, but its wildcard language is less powerful than PowerShell's and has legacy quirks - notably, character sets and ranges ([...]) are not supported and in Windows PowerShell a filter such as *.xls also matches *.xlsx files, for instance - see this answer.
For -Exclude:
Use only -Depth and perform filtering after the fact with a Where-Object call; e.g.,
Get-ChildItem -File 'C:\Program Files\' -Depth 1 | Where-Object Name -NotLike *.txt
[Probably rarely needed] For wildcard-based -Path arguments with wildcard characters (also) in a component other than the last one (e.g., C:\foo*\bar)
Use -Recurse and perform filtering after the fact with Where-Object; in this case, you'll also have to weed out too-deep paths by counting the number of their components.
The issue gets solved when you use Filter instead of Include. Filter parameter will return file in correct pattern with depth. (TESTED)
dir 'C:\Program Files\' -Filter *.txt -Depth 1
In older versions of PowerShell there was no depth, in that case the above can also be
Get-ChildItem -Path "C:\DIRECTORY\*","C:\DIRECTORY\*\*"
If it is pure for filenames then
(Get-ChildItem -Path "c:\program files" -file -Depth 3 -Force -erroraction SilentlyContinue).FullName
Is identical to the ancient kind of tricks, i.e.
(cmd.exe /c dir "c:\program files" /b /a-d /s)|foreach {if ($_.split("\").length -le 5){$_}}
It's amazing that PowerShell is even faster than the above line! I remember that a few years ago that was not the case, but I just tested it and it was 3-4 times faster
To further clarify the answer by Wasif Hasan
As I was going through the official documentation for the Get-ChildItem, it is stated there
When using the -Include parameter, if you do not include an asterisk
in the path the command returns no output.
Which means that the Depth will be ignored automatically as the behavior required for the Include is recursive. Further some details of the -Include reveals these points.
If the Recurse parameter is added to the command, the trailing
asterisk (*) in the Path parameter is optional. The Recurse parameter
gets items from the Path directory and its subdirectories. For
example, -Path C:\Test\ -Recurse -Include *.txt
So the behavior you are looking for is in the Filter flag for the Get-ChildItem which do not requires any wild cards
For me the Depth flag with any other flag that accepts wild cards in the path do not make sense as the purpose of the Depth flag is to restrict the depth of search in the Items where as specifying a wild card excludes that particular purpose.
You can try this by simply using this command and you will see that the Depth parameter is not effective if you specify a wild card in the path for example
Get-ChildItem -Path C:\DIRECTORY\* -Depth 1
and
Get-ChildItem -Path C:\DIRECTORY\* -Depth 2
are going to return the same results.
Hope this helps clarify some issues
I have the following two cmd.exe commands, but I need to convert them to Powershell, and I've failed miserably trying to figure it out. Line 1 is finding a dll, but only when in a bin folder and line two then takes all the entries it finds and runs a command with it, e.g. bin\Debug\file, bin\Release\file
Can anyone help? The only limitation is this is inside a yaml runner file so I don't think I can split lines for each part, e.g. I don't think a Foreach-Object will work.
dir /s /b RunnerUnitTest.dll | findstr /r bin\\ > tests_list.txt
for /f %f in (tests_list.txt) do vstest.console.exe "%f"
I got as far as this
(gci -r RunnerUnitTest.dll).FullName | select-string bin
thanks.
Write a multi-line powershell script to do the work and then call that script from your yaml runner.
powershell -file "c:\myscripts\runtests.ps1" "c:\mydlls\RunnerUnitTest.dll" "c:\mytests\tests_list.txt"
A single command (pipeline), spread across 3 lines for readability, using built-in command aliases for brevity (but the parameter names are spelled out, for long-term robustness):
gci -Recurse -Filter RunnerUnitTest.dll |
? FullName -match bin\\ |
% { vstest.console.exe $_.FullName }
gci -Recurse -Filter RunnerUnitTest.dll finds all RunnerUnitTest.dll in the current directory's subtree; -Filter makes for faster matching than using the (positionally implied) -Path parameter.
? FullName -match bin\\ uses ? (Where-Object) to test the .FullName (full path) property values of the input file-info objects for matching regex bin\\, i.e. for a literal bin\ substring, and only passes matching file-info objects on.
% { vstest.console.exe $_.FullName } uses % (ForEach-Object) to invoke vstest.console.exe with each matching file's full path.
Note that no intermediate file with a list of DLLs to process is created, because it isn't necessary.
If you need to pass the above to an explicit invocation of the PowerShell CLI, you'd do:
powershell -noprofile -command "gci -Recurse -Filter RunnerUnitTest.dll | ? FullName -match bin\\ | % { vstest.console.exe $_.FullName }"
If you're using PowerShell [Core] 6+, substitute pwsh for powershell.
I want to create a text file with all filenames of a certain filetype plus the filesize, recursively from a specified directory and all subdirectories.
For example: Listing all .jpg files plus their sizes from a huge photo-collection.
I have found several similar questions, but not this specific listing.
One did this with the full path name, but I don't need this and it would become very long.
Another lists all files, but without size.
Another lists all filenames with size, but I can't specify a filetype.
This PowerShell command creates the desired list, but I don't know how to limit it to a certain filetype (e.g. .jpg)
gci -rec|?{!$_.PSIsContainer}|%{"$($_.Fullname) $($_.Length)"} >filelist.txt
This batch file lists all .jpg's, but without showing the filesize.
dir /b /s z:\Filme\*.jpg > list1.txt
for /f "tokens=*" %%A in (list1.txt) do echo %%~nxA >> list.txt
del list1.txt
Could anyone edit one of these? so I get the desired list, or come up with a different solution?
Could anyone edit one of these so I get the desired list?
You are almost there with the batch script.
%~z1 will display the file size (in bytes).
You can also get rid of the temporary file by using a slightly different version of the for command.
Use the following batch file:
#echo off
setlocal
for /f "tokens=*" %%A in ('dir /b /s z:\Filme*.jpg') do (
if /i "%%~xf" equ ".jpg" echo %%~nxf %%~zf
) > list.txt
endlocal
Further Reading
An A-Z Index of the Windows CMD command line | SS64.com
Windows CMD Commands (categorized) - Windows CMD - SS64.com
Command Redirection, Pipes - Windows CMD - SS64.com
Dir - list files and folders - Windows CMD - SS64.com
For - Loop through command output - Windows CMD - SS64.com
If - Conditionally perform command - Windows CMD - SS64.com
Parameters / Arguments - Windows CMD - SS64.com
You know about the %%~nxA modifier, so I'm a bit surprised you didn't notice the %%~zA modifier.
To simplify it even more, use a for /R loop and don't use a temp file:
(for /R %%A in (*.jpg) do echo %%~nxA %%~zA)>list.txt
or if you need the full path\name, use %%~fA (explicite) or even just %%A
Text output:
Get-ChildItem -Path 'X:\PHOTO' -Filter '*.jp*g' -Recurse |
Where-Object {-not $_.PsIsContainer} |
Select-Object Name, Length |
Out-File -FilePath '.\FileList.txt'
CSV output:
Get-ChildItem -Path 'X:\PHOTO' -Filter '*.jp*g' -Recurse |
Where-Object {-not $_.PsIsContainer} |
Select-Object Name, Length |
Export-Csv -Path '.\FileList.csv' -NoTypeInformation
P.S. I've used *.jp*g wildcard that will also match *.jpeg files. Unfortunately, * wildcard matches zero or more symbols, so you can get files like zzz.jpXXXg in your list. There are other ways to filter Get-ChildItem output that don't suffer from this issue, such as filtering with pipeline and regex but they're slower: Where-Object {$_.Extension -match '^\.jp[e]{1}g$'}
Another option would be to not use the -Filter parameter, but the -Include instead where the wildcard pattern works as expected, like this:
PowerShell version 3.0 and up
Get-ChildItem 'z:\Filme' -File -Include '*.jpg' -Recurse |
Select FullName, Length |
Export-Csv '.\FileList.csv' -NoTypeInformation
PowerShell version below 3.0
Get-ChildItem 'z:\Filme' -Include '*.jpg' -Recurse |
Where-Object { !$_.PsIsContainer} |
Select FullName, Length |
Export-Csv '.\FileList.csv' -NoTypeInformation
Note that -Include only works if you also specify -Recurse or if you have the path end in \* like in Get-Childitem 'z:\Filme\*'.
Also, -Filter works faster than -Include (or -Exclude) parameters.
As stated in the docs:
"Filters are more efficient than other parameters, because the provider applies them when the cmdlet gets the objects. Otherwise, PowerShell filters the objects after they are retrieved."
I have never looked into the layout from the Where command, but if it does not alter between languages/locales, or technically if your layout is not too dissimilar to that of my test system, you could do it on your machine like this:
From the Command Prompt:
(For /F "Tokens=1,3*" %A In ('Where/T /R . *.jpg 2^>Nul')Do #Echo("%C","%A")>"list.txt"
From a batch file:
#(For /F "Tokens=1,3*" %%A In ('Where/T /R . *.jpg 2^>Nul')Do #Echo("%%C","%%A")>"list.txt"
Obviously if the layout from your Where command output differs there's still a possibility to adjust the Tokens and/or include delimiters to suit your target system.
In the examples above, I've used . to represent the current directory, you could of course change that to another relative path, e.g. ..\Pictures, or full path, e.g. C:\Users\Patrick\Pictures as necessary.
And a powershell option:
Ls -Filt '*.jpg' -Fo -Rec -EA SilentlyContinue|?{!$_.PSIsContainer -And $_.Extension -Eq '.jpg'}|Select FullName,Length|ConvertTo-CSV -NoT|Select -Skip 1|SC '.\list.txt'
This will also include e.g. system and hidden files, will not include files with extensions other than .jpg and will not include an unrequested header with that listing.
try this
Get-ChildItem "yourdir" -File -Filter '*.jpg' -Recurse |
Select FullName, Length |
Export-Csv '.\FileList.csv' -NoType
Given that Get-ChildItem -Path *.exe will show all the executables in the current directory, why doesn't Get-ChildItem -File -Include *.exe return the same result? Both commands are executed in the same directory, first command (with -Path) returns a list of executables but the second command (with -File) doesn't. (gci -File will list everything including the exe)
Get-ChildItem -File | gm #=> FileInfo
Get-ChildItem *.* | gm #=> DirectoryInfo and FileInfo
All the commands bellow return objects of type FileInfo
Get-ChildItem -File
Get-ChildItem *.* -Include *.exe
Get-ChildItem -Path *.exe
But mixing -File and -Include/-Exclude returns nothing, even though the -include is looking for a filetype:
Get-ChildItem -File -Include *.exe #=> Returns nothing
What am I missing here?
From TechNet:
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to the contents of a directory,
such as C:\Windows*, where the wildcard character specifies the
contents of the C:\Windows directory.
In other words, when you use the Include parameter, it does not automatically consider all files and directories unless you use the Path or the Recurse parameters. Notice, that when just using the Path parameter, you must include a wildcard to force it to consider the file and directory results underneath that path. I cannot think of why this is.
To get your examples to work, you would use one of the following (I'm dropping the File parameter because it seems redundant):
Get-ChildItem -Path * -Include *.exe
Get-ChildItem -Include *.exe -Recurse
The gist of my answer to your question is an opinion tho - from what I've seen the Include parameter should be removed - or its behavior repaired to match the default behavior of the Get-ChildItem cmdlet when used without parameters. There may be a good explanation to why it works this way, but I'm unaware of this.
If you drop the Include parameter from your examples, the behavior/results make more sense (to me):
Get-ChildItem -Path *.exe
In this case, we would only need the Exclude parameter to effectively cover all filtering requirements. Something like:
Get-ChildItem -Path *.exe -Exclude *system*
From documentation:
-Include
Retrieves only the specified items.
The value of this parameter qualifies
the Path parameter. Enter a path
element or pattern, such as "*.txt".
Wildcards are permitted.
The Include parameter is effective only when the command
includes the Recurse parameter or the
path leads to the contents of a
directory, such as C:\Windows*, where
the wildcard character specifies the
contents of the C:\Windows directory.
My first understanding was:
c:\test\a.txt
c:\test\b.txt
So to get 'a.txt' and 'b.txt' I can write:
gci -Path "c:\test\*" -Include "*.txt"
And this works. But now consider such hierarchy:
c:\test\a.txt
c:\test\b.txt
c:\test\c.txt\c.txt
The same command returns:
a.txt, b.txt, c.txt
The actual logic seems to be:
-Include used to match all entities specified by -Path. If matched element
is a file - return it. If matched
element is a folder, look inside and
return matching first level children.
Also, the documentation say:
The Include parameter is effective only when the command
includes the Recurse parameter or the
path leads to the contents of a
directory...
This is wrong as well. E.g.
gci -Path "c:\test" -Include "*.txt"
It returns nothing, while without -Include I get folder content. So -Include is definitely "effective". What really happens here? The -Path specify the "c:\test", and the -Include tries to match this path. As "*.txt" does not match "test", so nothing returned. But look at this:
gci -Path "c:\test" -Include "*t"
It returns a.txt, b.txt and c.txt as "*t" matched "test" and matched all child items.
After all, even knowing how Include works now, I don't understand when to use it. Why do I need it look to inside subfolders? Why should it be so complex?
You're confusing the use of -include. The -include flag is applied to the path, not the contents of the path. Without the use of the recursive flag, the only path that is in question is the path you specify. This is why the last example you gave works, the path c:\test has a t in the path and hence matches "*t".
You can verify this by trying the following
gci -path "c:\test" -in *e*
This will still produce all of the children in the directory yet it matches none of their names.
The reason that -include is more effective with the recurse parameter is that you end up applying the wildcard against every path in the hierarchy.
Try the -filter parameter (it has support for only one extension):
dir -filter *.txt
Tacking on to JaredPar's answer, in order to do pattern matching with Get-ChildItem, you can use common shell wildcards.
For example:
get-childitem "c:\test\t?st.txt"
where the "?" is a wildcard matching any one character or
get-childitem "c:\test\*.txt"
which will match any file name ending in ".txt".
This should get you the "simpler" behavior you were looking for.
I just asked a similar question and got three quick replies concerning the Get-Help for Get-ChildItem.
The answer is in the full description
of the command (Get-Help Get-ChildItem
-full):
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to
the contents of a directory, such as
C:\Windows*, where the wildcard
character specifies the contents of
the C:\Windows directory.
So the following would work without
recurse.
PS C:\foo> Get-childitem -path
"c:\foo*" -Include *.txt
From Stack Overflow question PowerShell Scripting - Get-ChildItem.
I hope this helps :-)
Including \* at the end of the path should work around the issue
PS C:\logfiles> Get-ChildItem .\* -include *.log
This should return .log files from the current working directory (C:\logfiles)
Alex's example above indicates that a directory with the name foo.log would also be returned. When I tried it, it wasn't but it's 6 years later and that could be from PS updates.
However, you can use the child item Mode to exclude directories I think.
PS C:\logfiles> Get-Childitem .\* -include *.log | where-object {$_.mode -notmatch "d"}
This should exclude anything with the 'directory' mode set.
get-childitem -include only works with -recursive or a wildcard in the path. I consider this a bug [Thought it was different in PS 6].