powershell how to loop over files and zip them - powershell

First time powershell development - have to say not intuitive for a linux scripter.
need to loop over files to zip them and getting errors - anyone have some feedback on a good one liner?
gci C:\temp -r *.csv |
Where-Object { $_.lastwritetime -lt (Get-date).AddDays(-10)} |
ForEach-Object {'c:\temp\bin\gzip.exe' $_.FullName}

You're missing the call (&) operator to actually execute the command in the string. If you don't do this, the string is printed out as the result of an expression evaluation (instead of as a command.)
gci C:\temp -r *.csv `
| Where-Object { ... } `
| ForEach-Object { & 'c:\temp\bin\gzip.exe' $_.FullName}
Incidentally, if you install the PowerShell Community Extensions (http://pscx.codeplex.com) then this becomes much simpler:
ls c:\temp -r *.csv `
| where { ... }
| write-gzip

Applications can be executed in Powershell just by giving their name / path, you don't have to enclose them in quotes as strings and then use iex or &:
c:\temp\bin\gzip.exe $_.FullName
would work for the zipping part.

The following version of your script could come in handy when you can't rely on external tools:
gci C:\temp -recurse *.csv |
Where-Object { $_.lastwritetime -lt (Get-date).AddDays(-10)} |
ForEach-Object {
$zip = $_.fullname -replace "\.csv",".zip";
new-item -type File $zip -force;
((new-object -com shell.application).namespace($zip)).copyhere($_.fullname)
}

Related

Move all even files to different directory | Powershell and regex

I'm trying to move all even files from the current location to the directory "foo", but I have a problem matching them with regex.
Filenames are in this format:
11.txt, 121.txt, 342.txt
The command I'm currently using is:
Get-ChildItem | Where-Object {$_.Name -match '^[0-9]*[02468]$'} | Move-Item -Destination .\foo
The previous command I was using works OK but only for two-digit files 1.txt-99.txt
Get-ChildItem | Where-Object {$_.Name -match '^[0-9]+[02468]'} | Move-Item -Destination .\foo
I tried at https://regex101.com/ site with .NET flavor and checked this regex ^[0-9]*[02468]$ and it works OK - matches all even numbers, but for some reason, I have a problem with above mention PS command...
An alternate method I have used previously for odds/evens is a division method. The solution Santiago has provided in the comments is also a valid regex method for this problem.
# Gets files and starts loop on files
Get-ChildItem "C:\Temp\AllFiles" -File | ForEach-Object {
# If the BaseName of file is divisible by 2, else
If($_.BaseName % 2 -eq 0) {
Move-Item -Destination "C:\Temp\Evens" -Path $_.FullName
} Else {
Move-Item -Destination "C:\Temp\Odds" -Path $_.FullName
}
}

Exclude all sub-directories from PowerShell Get-ChildItem -match

Goal
Exclude all sub-directories when running a PowerShell script that matches a filename regex pattern
Directory structure
/
- 2018-11-19.md
18-2/
- 2018-10-16.md
- 2019-01-14.md
- 2019-10-10.md
18-3/
- 2019-01-13.md
- 2019-04-25.md
PowerShell script
$file = '2018-11-19.md'
Get-ChildItem -recurse | where-object { $_.FullName -match '[0-9]{4}-[0-9]{2}-[0-9]{2}.md' } |
ForEach-Object {$fullname = $_.fullname; (Get-Content $_.fullname | foreach-object {
$_ -replace "apple", "orange"
}) | Set-Content $fullname}
(Get-Content $file | ForEach-Object {
$_ -replace '<p(.*?)>(.*?)</p>', '$2'
}) | Set-Content -Encoding Utf8 $file
Get-ChildItem -recurse | where-object { $_.FullName -match '[0-9]{4}-[0-9]{2}-[0-9]{2}.md' } |
foreach-Object {$fullname2 = $_.fullname; (Get-Content $_.fullname |
pandoc -f markdown -t markdown -o $fullname2 $fullname2
)}
Details
The goal is to run the PowerShell script on only file(s) in the root directory. These file(s) at root will change but always be named according to the convention shown. The regex in the PowerShell script successfully matches this filename.
Currently the script changes all files in the directory example above.
Any examples I can find show how to exclude specific directories by identifying their names in the script (e.g., -Exclude folder-name). I want to exclude all sub-directories without naming them specifically because...
...In the future sub-directories may be added for 18-4, 19-5, etc., so it seems like an exclusion based on a regex would make sense.
Attempts
To limit the script's scope to the root directory, I tried variations on -notmatch with \, \*, \*.*, \\*, etc., without success.
To exclude sub-directories, I tried variations on -Exclude with the same paths but did not succeed.
My PowerShell knowledge is not advanced enough to get further than this. I would be grateful for any help or to be pointed in the right direction. Thank you for any help.
As pointed out by Owain and gvee in the comments, when using the -Recurse switch, you tell the Get-ChildItem cmdlet that you wish to traverse through the sub directory structure from the selected location. As expained on the docs site of the cmdlet
Gets the items in the specified locations and in all child items of the locations.
So simply removing the switch should make the code do as you want.
If you ever want only X level of sub directories you can use -Depth switch.
Get-ChildItem | where-object { $_.FullName -match '[0-9]{4}-[0-9]{2}-[0-9]{2}.md' } |
ForEach-Object {$fullname = $_.fullname; (Get-Content $_.fullname | foreach-object {
$_ -replace "apple", "orange"
}) | Set-Content $fullname}
(Get-Content $file | ForEach-Object {
$_ -replace '<p(.*?)>(.*?)</p>', '$2'
}) | Set-Content -Encoding Utf8 $file
Get-ChildItem | where-object { $_.FullName -match '[0-9]{4}-[0-9]{2}-[0-9]{2}.md' } |
foreach-Object {$fullname2 = $_.fullname; (Get-Content $_.fullname |
pandoc -f markdown -t markdown -o $fullname2 $fullname2
)}

Get-ChildItem error handling when using long file paths

I am trying to handle errors when scanning through folders. Let's say I have something like:
Get-ChildItem $somepath -Directory | ForEach-Object {
if(error occurred due to too long path) {
skip this folder then
} else {
Write-Host $_.BaseName
}
}
When I do this I print the folders in $somepath until one of them is too long and then the loop stops. Even when using SilentlyContinue. I want to print even after reaching a folder that is too long.
If you can install a non-ancient PowerShell version (3.0 or newer), simply prepend the path with \\?\ to overcome the 260-character limit for full path:
Get-ChildItem "\\?\$somepath" | ForEach {
# ............
}
You could try ignoring the files longer 260 characters by using the Where-Object cmdlet.
Get-ChildItem $somepath -Directory -ErrorAction SilentlyContinue `
| Where-Object {$_.length -lt 261} `
| ForEach-Object { Write-Host $_.BaseName }
Or you could use the following (Ref).
cmd /c dir $somepath /s /b | Where-Object {$_.length -lt 261}
I will add my solution since the neither on this page worked for me. I am using relative paths, so I can't use the \\ prefix.
$TestFiles = Get-ChildItem $pwd "*Tests.dll" -Recurse | Where-Object {$_.FullName.length -lt 261} | Select-Object FullName -ExpandProperty FullName | Where-Object FullName -like "*bin\Release*"
Write-Host "Found TestFiles:"
foreach ($TestFile in $TestFiles) {
Write-Host " $TestFile"
}

How to compress multiple files into one zip with PowerShell?

I want to compress multiple files into one zip.
I am stuck with this at the moment:
Get-ChildItem -path C:\logs -Recurse | Where {$_.Extension -eq ".csv" -and $_.LastWriteTime -lt (Get-Date).AddDays(-7)} | write-zip -level 9 -append ($_.LastWriteTime).zip | move-item -Force -Destination {
$dir = "C:\backup\archive"
$null = mkdir $dir -Force
"$dir\"
}
I get this exception
Write-Zip : Cannot bind argument to parameter 'Path' because it is null.
This part is the problem:
write-zip -level 9 -append ($_.LastWriteTime).zip
I have never used powershell before but i have to provide a script, I can't provide a c# solution.
The problem is that Get-ChildItem returns instances of the System.IO.FileInfo class, which doesn't have a property named Path. Therefore the value cannot be automatically mapped to the Path parameter of the Write-Zip cmdlet through piping.
You'll have to use the ForEach-Object cmdlet to zip the files using the System.IO.FileInfo.FullName property, which contains the full path:
Get-ChildItem -Path C:\Logs | Where-Object { $_.Extension -eq ".txt" } | ForEach-Object { Write-Zip -Path $_.FullName -OutputPath "$_.zip" }
Here's a shorter version of the command using aliases and positional parameters:
dir C:\Logs | where { $_.Extension -eq ".txt" } | foreach { Write-Zip $_.FullName "$_.zip" }
Related resources:
Cool Pipeline Tricks, Redux (TechNet Magazine)

How do I get only directories using Get-ChildItem?

I'm using PowerShell 2.0 and I want to pipe out all the subdirectories of a certain path. The following command outputs all files and directories, but I can't figure out how to filter out the files.
Get-ChildItem c:\mypath -Recurse
I've tried using $_.Attributes to get the attributes but then I don't know how to construct a literal instance of System.IO.FileAttributes to compare it to. In cmd.exe it would be
dir /b /ad /s
For PowerShell 3.0 and greater:
Get-ChildItem -Directory
You can also use the aliases dir, ls, and gci
For PowerShell versions less than 3.0:
The FileInfo object returned by Get-ChildItem has a "base" property, PSIsContainer. You want to select only those items.
Get-ChildItem -Recurse | ?{ $_.PSIsContainer }
If you want the raw string names of the directories, you can do
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | Select-Object FullName
In PowerShell 3.0, it is simpler:
Get-ChildItem -Directory #List only directories
Get-ChildItem -File #List only files
Use
Get-ChildItem -dir #lists only directories
Get-ChildItem -file #lists only files
If you prefer aliases, use
ls -dir #lists only directories
ls -file #lists only files
or
dir -dir #lists only directories
dir -file #lists only files
To recurse subdirectories as well, add -r option.
ls -dir -r #lists only directories recursively
ls -file -r #lists only files recursively
Tested on PowerShell 4.0, PowerShell 5.0 (Windows 10), PowerShell Core 6.0 (Windows 10, Mac, and Linux), and PowerShell 7.0 (Windows 10, Mac, and Linux).
Note: On PowerShell Core, symlinks are not followed when you specify the -r switch. To follow symlinks, specify the -FollowSymlink switch with -r.
Note 2: PowerShell is now cross-platform, since version 6.0. The cross-platform version was originally called PowerShell Core, but the the word "Core" has been dropped since PowerShell 7.0+.
Get-ChildItem documentation: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem
A cleaner approach:
Get-ChildItem "<name_of_directory>" | where {$_.Attributes -match'Directory'}
I wonder if PowerShell 3.0 has a switch that only returns directories; it seems like a logical thing to add.
Use:
dir -r | where { $_ -is [System.IO.DirectoryInfo] }
From PowerShell v2 and newer (k represents the folder you are beginning your search at):
Get-ChildItem $Path -attributes D -Recurse
If you just want folder names only, and nothing else, use this:
Get-ChildItem $Path -Name -attributes D -Recurse
If you are looking for a specific folder, you could use the following. In this case, I am looking for a folder called myFolder:
Get-ChildItem $Path -attributes D -Recurse -include "myFolder"
Less text is required with this approach:
ls -r | ? {$_.mode -match "d"}
The accepted answer mentions
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | Select-Object FullName
to get a "raw string".
But in fact objects of type Selected.System.IO.DirectoryInfo will be returned. For raw strings the following can be used:
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | % { $_.FullName }
The difference matters if the value is concatenated to a string:
with Select-Object suprisingly foo\#{FullName=bar}
with the ForEach-operator the expected: foo\bar
Use:
dir -Directory -Recurse | Select FullName
This will give you an output of the root structure with the folder name for directories only.
You'll want to use Get-ChildItem to recursively get all folders and files first. And then pipe that output into a Where-Object clause which only take the files.
# one of several ways to identify a file is using GetType() which
# will return "FileInfo" or "DirectoryInfo"
$files = Get-ChildItem E:\ -Recurse | Where-Object {$_.GetType().Name -eq "FileInfo"} ;
foreach ($file in $files) {
echo $file.FullName ;
}
Use:
Get-ChildItem \\myserver\myshare\myshare\ -Directory | Select-Object -Property name | convertto-csv -NoTypeInformation | Out-File c:\temp\mydirectorylist.csv
Which does the following
Get a list of directories in the target location:
Get-ChildItem \\myserver\myshare\myshare\ -Directory
Extract only the name of the directories:
Select-Object -Property name
Convert the output to CSV format:
convertto-csv -NoTypeInformation
Save the result to a file:
Out-File c:\temp\mydirectorylist.csv
A bit more readable and simple approach could be achieved with the script below:
$Directory = "./"
Get-ChildItem $Directory -Recurse | % {
if ($_.Attributes -eq "Directory") {
Write-Host $_.FullName
}
}
Hope this helps!
My solution is based on the TechNet article Fun Things You Can Do With the Get-ChildItem Cmdlet.
Get-ChildItem C:\foo | Where-Object {$_.mode -match "d"}
I used it in my script, and it works well.
This question is well and truly answered but thought I'd add something extra as I've just been looking at this.
Get-ChildItem happens to produce two types of objects whereas most commands produce just one.
FileInfo and DirectoryInfo are returned.
You can see this by viewing the 'members' available to this command like so:
Get-ChildItem | Get-Member
TypeName: System.IO.DirectoryInfo
TypeName: System.IO.FileInfo
You'll see the various methods and properties available to each type. Note that there are differences. For example that the FileInfo object has a length property but the DirectoryInfo object doesn't.
Anyway, technically, we can return just the directories by isolating the DirectoryInfo object
Get-ChildItem | Where-Object {$_.GetType().Name -eq "DirectoryInfo"}
Obviously as the top answer states the most straightforward solution is to simply use Get-ChildItem -Directory but we now know how to work with multple object types in future :)
Use this one:
Get-ChildItem -Path \\server\share\folder\ -Recurse -Force | where {$_.Attributes -like '*Directory*'} | Export-Csv -Path C:\Temp\Export.csv -Encoding "Unicode" -Delimiter ";"
You can try the PsIsContainer Object
Get-ChildItem -path C:\mypath -Recurse | where {$_.PsIsContainer -eq $true}
To answer the original question specifically (using IO.FileAttributes):
Get-ChildItem c:\mypath -Recurse | Where-Object {$_.Attributes -band [IO.FileAttributes]::Directory}
I do prefer Marek's solution though:
Where-Object { $_ -is [System.IO.DirectoryInfo] }