Recurse with PowerShell's Get-ChildItem - powershell

This seems like it should be simple, and I'm sure it is, but I've not cracked it the best way it should be done.
I want to search through a folder structure returning folders which meet the following conditions.
Folders which contain .msi files, but don't include executable files.
Folders which contain .exe files, but don't include .msi files.
Folders which contain both exe and msi files.
Each of these will be piped to a column in a CSV file.
My problem is I can't work out how to effectively return folder names which include one file type, but exclude another. I know on paper this seems simple using -include *.msi, -exclude *.exe, etc., but a command such as gci -Recurse -Include *.msi -Exclude *.exe includes folders containing an msi and exe folder where I only want that folder containing msi's only to be returned.
I am using the following directory structure as a test
msi only
msi and exe
exe only
Does this make sense?
I was experimenting with | where directory -notcontains *.exe and all kinds of similar things, but none of them worked the way I wanted or expected.

Unfortunately include and exclude only work with the recurse parameter. You won't be able to do exclusion without recursion.
Here's an alternative.
$folders = dir -path ~ -recurse | ? {$_.PsIsContainer}
$folders | ForEach-Object {
$exe_files = $_ | dir -filter *.exe
$msi_files = $_ | dir -filter *.msi
$type = ''
if ($exe_files -and $msi_files) {$type = 'Both'}
if ($msi_files -and -not $exe_files) {$type = 'MSI_ONLY'}
if ($exe_files -and -not $msi_files) {$type = 'EXE_ONLY'}
if ($type) {
New-Object -TypeName PsObject -Property #{Path=$_.FullName;Type="$type"}
}
} | ConvertTo-Csv | Set-Content ~\out.csv

Related

Powershell: Find Folders and Run Command in Those Folders

so trying to find a way to combine a couple of things the Stack Overflow crowd has helped me do in the past. So I know how to find folders with a specific name and move them where I want them to go:
$source_regex = [regex]::escape($sourceDir)
(gci $sourceDir -recurse | where {-not ($_.psiscontainer)} | select -expand fullname) -match "\\$search\\" |
foreach {
$file_dest = ($_ | split-path -parent) -replace $source_regex,$targetDir
if (-not (test-path $file_dest)){mkdir $file_dest}
move-item $_ -Destination $file_dest -force -verbose
}
And I also know how to find and delete files of a specific file extension within a preset directory:
Get-ChildItem $source -Include $searchfile -Recurse -Force | foreach{ "Removing file $($_.FullName)"; Remove-Item -force -recurse $_}
What I'm trying to do now is combine the two. Basically, I'm looking for a way to tell Powershell:
"Look for all folders named 'Draft Materials.' When you find a folder with that name, get its full path ($source), then run a command to delete files of a given file extension ($searchfile) from that folder."
What I'm trying to do is create a script I can use to clean up an archive drive when and if space starts to get tight. The idea is that as I develop things, a lot of times I go through a ton of incremental non-final drafts (hence folder name "Draft Materials"), and I want to get rid of the exported products (the PDFs, the BMPs, the AVIs, the MOVs, atc.) and just leave the master files that created them (the INDDs, the PRPROJs, the AEPs, etc.) so I can reconstruct them down the line if I ever need to. I can tell the script what drive and folder to search (and I'd assign that to a variable since the backup location may change and I'd like to just change it once), but I need help with the rest.
I'm stuck because I'm not quite sure how to combine the two pieces of code that I have to get Powershell to do this.
If what you want is to
"Look for all folders named 'Draft Materials.' When you find a folder with that name, get its full path ($source), then run a command to delete files of a given file extension ($searchfile) from that folder."
then you could do something like:
$rootPath = 'X:\Path\To\Start\Searching\From' # the starting point for the search
$searchFolder = 'Draft Materials' # the folder name to search for
$deleteThese = '*.PDF', '*.BMP', '*.AVI', '*.MOV' # an array of file patterns to delete
# get a list of all folders called 'Draft Materials'
Get-ChildItem -Path $rootPath -Directory -Filter $searchFolder -Recurse | ForEach-Object {
# inside each of these folders, get the files you want to delete and remove them
Get-ChildItem -Path $_.FullName -File -Recurse -Include $deleteThese |
Remove-Item -WhatIf
}
Or use Get-ChildItem only once, having it search for files. Then test if their fullnames contain the folder called 'Draft Materials'
$rootPath = 'X:\Path\To\Start\Searching\From'
$searchFolder = 'Draft Materials'
$deleteThese = '*.PDF', '*.BMP', '*.AVI', '*.MOV'
# get a list of all files with extensions from the $deleteThese array
Get-ChildItem -Path $rootPath -File -Recurse -Include $deleteThese |
# if in their full path names the folder 'Draft Materials' is present, delete them
Where-Object { $_.FullName -match "\\$searchFolder\\" } |
Remove-Item -WhatIf
In both cases I have added safety switch -WhatIf so when you run this, nothing gets deleted and in the console is written what would happen.
If that info shows the correct files are being removed, take off (or comment out) -Whatif and run the code again.

Powershell Error in deleting files and folders

I have a scenario where the powershell script should be deleting the log files and log folders in a path, lets say they are under the path C:\MLA\logs.
Below is the script that I have been using, it completes removes the files but the problem is the script does not work for deleting the folders, the error it displays is something like could not find part of the path
C:\MLA\logs\ART_Daily.
below is the script
$root=C:\MLA\logs
$limit=(Get-Date).AddDays(-90)
get-childitem -Path $root -Recurse -force |
where-Object {(($_.name -match 'Daily|ART|ABC|IIC') -or ($_.PSIsContainer -match 'Daily|ART|ABC|IIC')) -and ($_.CreationTime -lt $limit)} |Remove-Item -recurse -Force
The $name checks for files ( if the names piped are part of the file name for any of the file ) in the root path and $.PSIsContainer check for folders 9 f the names piped are part of the folder name for any of the folder ) inside the root path which is parametrized.
Can you help me out.
You need to correct your filter for starters:
$_.name -match 'Daily|ART|ABC|IIC'
will match files and folders with that name.
$_.PSIsContainer -match 'Daily|ART|ABC|IIC'
Will find nothing because the PSIsContainer property is boolean (is it a container or not: True/False?).

How can Powershell copy an entire folder structure but exclude one folder and its contents

This seems like a simple operation but I can't figure out how to get Powershell to copy an entire folder structure from one location to another but exclude one folder (called 'connections') and its contents.
I've tried combining Copy-Item and Get-ChildItem like so
cpi (gci folder1 -Exclude connections) folder2 -recurse
but it seems the -recurse parameter overwrites the -exclude parameter and the connections folder and its contents are copied. Without -recurse the contents of the folders I do want copied are ignored.
I'm not sure why that isn't working, it seems to behave correctly on my machine.
You could always pipe to Copy-Item:
Get-ChildItem folder1 | where { !(($_ -is [System.IO.DirectoryInfo]) -and ($_.Name -eq "connections")) } | Copy-Item -Destination folder2 -Recurse
The advantage of this is that you can just get PowerShell to print out the output after:
Get-ChildItem folder1 | where { !(($_ -is [System.IO.DirectoryInfo]) -and ($_.Name -eq "connections")) }
That way you can check exactly what is getting copied (i.e. is the "connections" folder missing?)

Powershell script for comparing two directories based on file names only

I have two disks which has the same directory structure. C:\Files and D:\Files
Both C:\Files and D:\Files have multiple directories under them but have the same name etc, but the files inside them differ in extension. In C:\Files they are *.csv and in D:\Files, a process monitors the files (copied from C:\) and once it is done changes the files to *.processed.
I want a script that would do that copy. I.e copy files from C:\Files to D:\Files which have not been processed by comparing only the file names.
You want something like this. The property you want to compare on is called BaseName which powershell helpfully adds for you to the IO.FileSystemInfo class (FileInfo.BaseName exists in PowerShell but not in straight .NET). BaseName is just the name of the file, and doesn't contain any of the extensions that you don't care about.
$sourceFiles = Get-ChildItem C:\files -Recurse -File -Filter "*.csv"
$destinationFiles = Get-ChildItem D:\files -Recurse -File
foreach($sourceFile in $sourceFiles) {
$exists = $destinationFiles | Where-Object {$_.BaseName -eq $sourceFile.BaseName}
if(!$exists) {
Copy-Item $sourceFile.fullname -Destination "D:\Files\$($sourceFile.BaseName)"
}
}
dir .\ *csv -Recurse -File | cp -ea Ignore -WhatIf -Destination {
$dest=$_.FullName-replace'^C:','D:'-replace'\.csv','.processed'
if(Test-Path $dest){
Write-Host Already exists -ForegroundColor Yellow
$null
}else{
Write-Host Copying... -ForegroundColor Green
$dest-replace'\.processed$','.csv'
}
}
Notice the WhatIf parameter: you must remove it, if you're going to really copy the items.
Also, you may like to remove the 2 lines withwrite-host cmdlet.
Notive too, that I have hard-coded the C: and D: drives as source and destine drives.

XCOPY deployment script - how to include certain files?

I need to copy only certain parts of a folder using Powershell, specifically this list:
$files = #("MyProgram.exe",
"MyProgram.exe.config",
"MyProgram.pdb",
".\XmlConfig\*.xml")
In human readable form: 3 specific MyProgram.* files under root of target folder and all XML files under XmlConfig folder which itself is under root of source path (..\bin\Release\ in my case). XmlConfig folder must be created in destination, if it does not exist.
What I have tried:
(1) I tried the following, but it did not work, i.e. no folder or files were created at the destination path:
Copy-Item -Recurse -Path "..\bin\Release\" -Destination ".\Test\" -Include $files
(2) When -Include is removed, whole folder structure is successfully created, including subfolders and files:
Copy-Item -Recurse -Path "..\bin\Release\" -Destination ".\Test\"
It must be something wrong with my understanding of how -Include filter works:
(3) I tested an assumption that -Include needs an array of wildcards, but this did not work either:
$files = #("*MyProgram.exe*",
"*MyProgram.exe.config*",
"*MyProgram.pdb*",
"*.\XmlConfig\*.xml*")
Please advise on how to properly do Copy-Item in my case.
UPDATE (based on below answers):
I am looking for a generic implementation that takes an array of strings. It opens the possibility to put all necessary files/paths in one place, for easy editing, so that a non-Powershell knowledgeable person can understand and modify it as required. So in the end it would be single script to perform XCOPY deployments for any project, with input file being the only variable part. For above example, the input would look like this (saved as input.txt and passed as an argument to the main script):
MyProgram.exe
MyProgram.exe.config
MyProgram.pdb
.\XmlConfig\*.xml
I would prefer wildcards approach, since not many people know regex.
i don't know what is wrong with filter but you can still do
$files | % { copy-item ..\bin\release\$_ -Destination .\test}
if you want to preserve directoty structure you'll have to weak this a little, like :
$sourcedir="c:\temp\test"
$f=#("existing.txt","hf.csv";"..\dir2\*.txt")
$f |%{
$source=ls (join-Path $sourcedir $_) |select -expand directoryname
if ("$source" -like "$sourcedir*"){
$destination=$source.Substring($sourcedir.Length)+".\"
}
else{
$destination=$_
}
copy-item $sourcedir\$_ -Destination $destination -WhatIf
}
AFAICT -Include works only with file names or directory names and not combinations i.e. paths. You can try something like this:
$files = 'MyProgram\.exe|MyProgram\.exe\.config|MyProgram\.pdb|XmlConfig\\.*?\.xml'
Get-ChildItem ..\bin\release -r | Where {!$_.PSIsContainer -and ($_.FullName -match $files)} |
Copy-Item -Dest .\test
With wildcards you could do it this way:
$files = #('*MyProgram.exe','*MyProgram.exe.config','*MyProgram.pdb','*\XmkConfig\*.xml')
Get-ChildItem ..\bin\release -r |
Foreach {$fn=$_.Fullname;$_} |
Where {!$_.PSIsContainer -and ($files | Where {$fn -like $_})} |
Copy-Item -Dest .\test