Move files that contain a string to a subfolder with the same name as the original (PowerShell) - string

I'm using PowerShell and it is two days that I'm struggling on this issue.
In the directory C:\dir_1 I have many subfolders (sub_1, sub_2, ..., sub_n). Each of them contains several text files. For each subfolder i=1,2,...,n, I want to move the text files that contain the string "My-String" to the directory C:\dir_2\sub_i.
For example, if the file X in the path C:\dir1\sub_5 contains the string "My-String", I want to move it to the location C:\dir_2\sub_5. The destination folder is already existing.
I tried several modifications of the following code, but it does not work:
Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True} | Foreach-Object {Get-ChildItem "C:\dir_1\$_" | Select-String -pattern "My-String" | group path | select name | %{Move-Item $_.name "C:\dir_2\$_"}}
So, basically, what I tried to do is: foreach subfolder in dir_1, take the files that contain the string and move them to the subfolder in dir_2 with the same name. I tried several small modifications of that code, but I cannot get around my mistakes. The main error is "move-item: The given path format is not supported"... any help?

I feel like I could do better but this is my first approach
$dir1 = "C:\temp\data\folder1"
$dir2 = "C:\temp\data\folder2"
$results = Get-ChildItem $dir1 -recurse | Select-String -Pattern "asdf"
$results | ForEach-Object{
$parentFolder = ($_.Path -split "\\")[-2]
Move-Item -Path $_.Path -Destination ([io.path]::combine($dir2,$parentFolder))
}
Select-String can take file paths for its pipeline input. We feed it all the files that are under $dir1 using -recurse to get all of its children in sub folders. $results would contain an array of match objects. One of the properties is the path of the matched file.
With all of those $results we then go though each and extract the parent folder from the path. Then combine that folder with the path $dir2 in order to move it to it destination.
There are several assumptions that we are taking here. Some we could account for if need be. I will mention the one I know could be an issue first.
Your folders should not have any other subfolders under "sub_1, sub_2, ..., sub_n" else they will attempt to move incorrectly. This can be addressed with a little more string manipulation. In an attempt to make the code terse using -Recurse created this caveat.

Here is a one liner that does what you want too:
Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True} | ForEach-Object {$SubDirName = $_.Name;ForEach ($File in $(Get-ChildItem $_.FullName)){If ($File.Name -like "*My-String*"){Move-Item $File.FullName "C:\dir_2\$SubDirName"}}}
And if you'd like to see it broken out like Matt's answer:
$ParentDir = Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True}
ForEach ($SubDir in $ParentDir){
$SubDirName = $SubDir.Name
ForEach ($File in $(Get-ChildItem $SubDir.FullName)){
If ($File.Name -like "*My-String*"){
Move-Item $File.FullName "C:\dir_2\$SubDirName"
}
}
}

Related

How to modify the file creation time of multiple files in single script

I'm stumped on this. I am making my way through a file share migration to SharePoint. There have been errors stating the "The item created time or modified time is not supported". No worries as I found a script to edit this in PowerShell:
cd "Directory"
Get-ChildItem -force | Select-Object Mode, Name, CreationTime, LastAccessTime, LastWriteTime | ft
$modifyfiles = Get-ChildItem -force | Where-Object {! $\_.PSIsContainer}
foreach($object in $modifyfiles)
{
$object.CreationTime=("1/3/2023 12:00:00")
$object.LastAccessTime=("1/3/2023 12:01:00")
$object.LastWritetime=("1/3/2023 12:02:00")
}
My question is how do I run this so I don't have to cd to each new directory every time. I have quite a few files in different folders that all need editing. I have the list of paths I need changed and I was hoping there would be a way to "pass" those paths in or somehow run this script in a loop.
Assuming your list of folders looks like this and can be placed in a seperate text file:
C:\folderpath\folder1
C:\folderpath\folder2
C:\folderpath\folder3
Then you could just do something like this:
get-content -Path "C:\folderpath\FileContainingFolderPaths.txt" | ForEach-Object {
$folderpath = $_
Get-ChildItem -Path $folderpath -force | Select-Object Mode, Name, CreationTime, LastAccessTime, LastWriteTime | ft
$modifyfiles = Get-ChildItem -Path $folderpath -force | Where-Object {! $\_.PSIsContainer}
foreach($object in $modifyfiles)
{
$object.CreationTime=("1/3/2023 12:00:00")
$object.LastAccessTime=("1/3/2023 12:01:00")
$object.LastWritetime=("1/3/2023 12:02:00")
}
}
Also I'm gonna have to question you on the $\_.PSIsContainer, is that a mistake? I would think it should be $_.PSIsContainer instead?

Date content comparison if it is greater then todays date

I am looking for some script in PowerShell that will compare the date present in an inside text file as content and compare if that date is >today`+15 days then print the file name.
Also, if that script can compare the date as mentioned above along with the other string if both conditions are matching then print the file name.
The below command gives me the output for those which have matching string same as hello and was created 30 days back. But now I want to fulfill the above two conditions no matter when the file was created.
Get-ChildItem -Path C:\Users\vpaul\Downloads\functional-script\*.txt -Recurse | Select-String -Pattern 'Hello', 'Hell' | Where CreationTime -lt (Get-Date).AddDays(-6)| Export-Csv C:\Users\vpaul\Downloads\functional-script\File_Name.csv -NoTypeInformation
The output from Select-String doesn't have a CreationTime property, which is why your filtering fails - CreationTime doesn't resolve to anything so it's always "less than" any value you provide.
Either do the filtering on CreationTime before piping to Select-String:
Get-ChildItem ... |Where-Object CreationTime -lt (Get-Date).AddDays(-6) |Select-String 'Hell' | ...
Or use the Path property on the output from Select-String to look up the files attributes again:
Get-ChildItem ... |Select-String 'Hell' |Where-Object {(Get-ItemPropertyValue -LiteralPath $_.Path -Name CreationTime) -lt (Get-Date).AddDays(-6)} |...
Since it looks like you're trying to get and compare a date from a matched text string inside the file, as well as CreationTime file attribute... +15 Days and -6 Days respectively...
Example Text file Content:
Hello 4/1/2021
You could try something similar to this:
$ALL_RECURSED_TXTs = Get-ChildItem -Path '[Folder to Recurse]\*.txt' -Recurse | Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-6) };
foreach($File in $ALL_RECURSED_TXTs) {
Get-Content -Path $File.FullName | Select-String -Pattern 'Hello', 'Hell' |
ForEach-Object {
# Find a RegEx match for your Date String that is in the File
$_ -match 'Hello\s(\d+\/\d+\/\d{4}).*' | Out-Null;
if((Get-date($matches[1])) -gt ((Get-Date).AddDays(15))) {
"$($File.FullName)" | Out-File -FilePath '[Path to Output]\MyPrintedFileNames.txt' -Append;
}
}
}
If you want to see your matched lines in your outfile...
"$_ : $($File.FullName)" | Out-File -FilePath '[Path to Output]\MyPrintedFileNames.txt' -Append;
"but now I want to fulfill the above two conditions no matter when the file was created."
Scrap the Where-Object filter on Get-ChildItem if you want all txt files.
Edit: Getting confused again. Lol. If your txt file date string is not on same line as your "Hello|Hell" it'll get more complex. Good Luck!

Partial string match and folder creation using PowerShell

I have a directory with thousands of files, and I would like to create new directories based on part of the file name, and then sort the partial matching files into those directories.
The files all start with the same prefix, "123-", and then have numbers random and ranging in length, but I do not want whatever is after the numbers. I tried regular expressions, but I am having issues with matching the varying number lengths after the "123-" and skipping everything after the middle number.
Example files:
directory\123-4441Zebra.pdf
directory\123-4441Tango.pdf
directory\123-768987Beta.pdf
directory\123-768987Tango.pdf
directory\123-34263XYP.pdf
Example Result:
directory\123-4441\123-4441Zebra.pdf
directory\123-4441\123-4441Tango.pdf
directory\123-768987\123-768987Beta.pdf
directory\123-768987\123-768987Tango.pdf
directory\123-34263\123-34263XYP.pdf
Try this:
$yourdir="C:\temp\root"
gci $yourdir -File -Filter 123-*.pdf | %{$newdir=$yourdir + "\" + ($_.Name -replace '(123-\d+).*', '$1'); New-Item -Path $newdir -Force -ItemType Directory; Move-Item -Path $_.FullName -Dest $newdir -Force }
PS C:\> '123-4441Zebra.pdf' -replace '(123-\d+).*', '$1'
123-4441
e.g.
gci "c:\place\" | mv -Dest { "c:\out\$($_.Name -replace '(123-\d+).*', '$1')\" } -Force -WhatIf
Auto-generated PS help links from my codeblock (if available):
gci is an alias for Get-ChildItem (in module Microsoft.PowerShell.Management)
mv is an alias for Move-Item (in module Microsoft.PowerShell.Management)

Outputting PowerShell data to a string

This is really PowerShell 101, I realise, but I'm stuck.
I'm trying to iterate through a folder tree, getting each subfolder name and a count of files. No problems there.
The new requirement is to get the ACLs on each subfolder as well. All of this data needs to be output as a CSV file, with a line consisting of each folder name, the file count, and the ACLs in a single string in one field of the CSV (I was going to delimit them with semicolons).
I am open to exporting to XML if the data can be viewed in Excel.
The part where I'm stuck is getting the ACL information into a single string for the CSV.
Get-ACL on each directory shows the data as follows (I'm doing a Select to just get the IdentityReference and FileSystemRights, which is all we're interested in):
IdentityReference FileSystemRights
----------------- ----------------
BUILTIN\Users ReadAndExecute, Synchronize
BUILTIN\Users AppendData
BUILTIN\Users CreateFiles
I would like the output file formatted with one line per subdirectory, similar to
#filecount,folder,perms
51,C:\temp,BUILTIN\Users:ReadAndExecute,Synchronize;BUILTIN\Users:AppendData...
I however can't get any kind of join working to have it presented in this way. I don't care about what combination of delimiters are used (again, must be readable in Excel).
The script, such as it is, is as follows. The output file has its line of data appended with each directory it traverses. I'm sure this isn't very efficient, but I don't want the process consuming all the server memory either. The bits I can't figure out are prepended with ###.
(Get-ChildItem C:\temp -recurse | Where-Object {$_.PSIsContainer -eq $True}) | foreach {
$a = ($_.GetFiles().Count)
$f = $_.FullName
$p = (get-acl $_.FullName).Access | select-object identityreference,filesystemrights
### do something with $p?
Out-File -FilePath c:\outfile.csv -Append -InputObject $a`,$f`,###$p?
}
Since you want all ACEs of a folder mangled into a single line you need something like this:
Get-ChildItem 'C:\temp' -Recurse | ? { $_.PSIsContainer } | % {
# build a list of "trustee:permissions" pairs
$perms = (Get-Acl $_.FullName).Access | % {
"{0}:{1}" -f $_.IdentityReference, $_.FileSystemRights
}
New-Object -Type PSObject -Property #{
'Filecount' = $_.GetFiles().Count
'Folder' = $_.FullName
'Permissions' = $perms -join ';' # join the list to a single string
}
} | Export-Csv 'c:\outfile.csv' -NoType
Repeated appending inside a loop usually guarantees poor performance, so it should be avoided whenever possible. The outer loop creates a list of custom objects, which can then be exported via Export-Csv in a single go.

Renaming many folders in PowerShell

I have over 1000+ files that have to be renamed.
The first set folder and/or files are grouped by location, so the first four characters are the same for each file; there are four-five different locations. I need to delete the first few characters of the folder's name.
Example:
Old File: ABC_Doe, Jane
New File: Doe, Jane
any suggestions as to the quickest way to carry this out?
I've tried all of the following:
1st Attempt
$a = Get-ChildItem C:\example
$b = Where-Object {$_.name -like “*ABC_*”}
$cmdlet_name = “Rename-Item”
$d = (cmdlet_name $a $b)
invoke-expression $d
2nd Attempt
$e = Get-ChildItem C:\example
$f = $e.TrimStart (“ABC_”)
3rd Attempt
Rename-Item -{$_.name -like “*ASD*”, “”}
Try this, get all child items (files only), remove abc_ by replacing them (with nothing) and rename each file. To rename files in sub-directories add the -Recurse switch to the Get-ChildItem command:
Get-ChildItem c:\example -Filter ABC_* | Where-Object {!$_.PSIsContainer} | Rename-Item -NewName { ($_.BaseName -replace '^ABC_') + $_.Extension }
UPDATE
Actually, this should work as well and is much shorter (no need to append the file extension cause renaming is performed on the file name).
Get-ChildItem c:\example -Filter ABC_* | Where-Object {!$_.PSIsContainer} | Rename-Item -NewName { $_.Name -replace '^ABC_' }
get-childItem ABC_* | rename-item -newname { $_.name -replace 'ABC_','' }
Source: get-help rename-item -full

Resources