I have found a script which does everything that I need, but it's only useful if you run it in a single folder. What I'd like is:
Script is located in c:/temp/. Upon running the script, it would go into each subfolder and execute. Each subfolder would then have a separate Final.csv.
Somebody mentioned just add -Recurse, but it doesn't complete the job as described. With -Recurse added, it goes into each subfolder and creates a Final.csv final in the root dir (C:/temp/) instead of creating a Final.csv in each subfolder.
$getFirstLine = $true
get-childItem *.csv | foreach {
$filePath = $_
$lines = Get-Content $filePath
$linesToWrite = switch($getFirstLine) {
$true {$lines}
$false {$lines | Select -Skip 2}
}
$getFirstLine = $false
Add-Content Final.csv $linesToWrite
}
If you are certain the csv files combined this way will leave you a valid 'Final.csv', you need to use Group-Object in order to create a combined file in each of the directories where the csv files to combine are found.
Suppose you have a folder with subfolders 'Folder1' and 'Folder2', both having csv files in them like these:
first.csv
Lorem,Ipsum,Dolor,Sic,Amet
data1-1,data1-2,data1-3,data1-4,data1-5
data2-1,data2-2,data2-3,data2-4,data2-5
second.csv
Lorem,Ipsum,Dolor,Sic,Amet
something,blah,whatever,very important,here's more..
Then this should do it for you:
$targetFileName = 'Final.csv'
# loop over the CSV files, but exclude the Final.csv file
# Group the files by their DirectoryNames
Get-ChildItem -Path 'D:\Test' -Filter '*.csv' -File -Recurse -Exclude $targetFileName | Group-Object DirectoryName | ForEach-Object {
# reset the $getFirstLine variable for each group
$getFirstLine = $true
# create the target path for the combined csv inside this folder.
# ($_.Name is the name of the group, which is the Directory name of the files inside the group)
$target = Join-Path -Path $_.Name -ChildPath $targetFileName
foreach ($file in $_.Group) {
if ($getFirstLine) {
# copy the first CSV as a whole
Get-Content -Path $file.FullName | Set-Content -Path $target
$getFirstLine = $false
}
else {
# add the content of the next file(s) without the header line
Get-Content -Path $file.FullName | Select-Object -Skip 1 | Add-Content -Path $target
}
}
}
The end result is that each subfolder will have a new 'Final.csv' file containing
Lorem,Ipsum,Dolor,Sic,Amet
data1-1,data1-2,data1-3,data1-4,data1-5
data2-1,data2-2,data2-3,data2-4,data2-5
something,blah,whatever,very important,here's more..
Of course I'm just showing an example for one of the subfolders.. Other subfolders will contain different 'Final.csv' content
Related
In a directory, there are files with the following filenames:
ExampleFile.mp3
ExampleFile_pn.mp3
ExampleFile2.mp3
ExampleFile2_pn.mp3
ExampleFile3.mp3
I want to iterate through the directory, and IF there is a filename that contains the string '_pn.mp3', I want to test if there is a similarly named file without the '_pn.mp3' in the same directory. If that file exists, I want to remove it.
In the above example, I'd want to remove:
ExampleFile.mp3
ExampleFile2.mp3
and I'd want to keep ExampleFile3.mp3
Here's what I have so far:
$pattern = "_pn.mp3"
$files = Get-ChildItem -Path '$path' | Where-Object {! $_.PSIsContainer}
Foreach ($file in $files) {
If($file.Name -match $pattern){
# filename with _pn.mp3 exists
Write-Host $file.Name
# search in the current directory for the same filename without _pn
<# If(Test-Path $currentdir $filename without _pn.mp3) {
Remove-Item -Force}
#>
}
enter code here
You could use Group-Object to group all files by their BaseName (with the pattern removed), and then loop over the groups where there are more than one file. The result of grouping the files and filtering by count would look like this:
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1
Count Name Group
----- ---- -----
2 ExampleFile {ExampleFile.mp3, ExampleFile_pn.mp3}
2 ExampleFile2 {ExampleFile2.mp3, ExampleFile2_pn.mp3}
Then if we loop over these groups we can search for the files that do not end with the $pattern:
#'
ExampleFile.mp3
ExampleFile_pn.mp3
ExampleFile2.mp3
ExampleFile2_pn.mp3
ExampleFile3.mp3
'# -split '\r?\n' -as [System.IO.FileInfo[]] | Set-Variable files
$pattern = "_pn"
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1 | ForEach-Object {
$_.Group.Where({-not $_.BaseName.Endswith($pattern)})
}
This is how your code would look like, remove the -WhatIf switch if you consider the code is doing what you wanted.
$pattern = "_pn.mp3"
$files = Get-ChildItem -Path -Filter *.mp3 -File
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1 | ForEach-Object {
$toRemove = $_.Group.Where({-not $_.BaseName.Endswith($pattern)})
Remove-Item $toRemove -WhatIf
}
I think you can get by here by adding file names into a hash map as you go. If you encounter a file with the ending you are interested in, check if a similar file name was added. If so, remove both the file and the similar match.
$ending = "_pn.mp3"
$files = Get-ChildItem -Path $path -File | Where-Object { ! $_.PSIsContainer }
$hash = #{}
Foreach ($file in $files) {
# Check if file has an ending we are interested in
If ($file.Name.EndsWith($ending)) {
$similar = $file.Name.Split($ending)[0] + ".mp3"
# Check if we have seen the similar file in the hashmap
If ($hash.Contains($similar)) {
Write-Host $file.Name
Write-Host $similar
Remove-Item -Force $file
Remove-Item -Force $hash[$similar]
# Remove similar from hashmap as it is removed and no longer of interest
$hash.Remove($similar)
}
}
else {
# Add entry for file name and reference to the file
$hash.Add($file.Name, $file)
}
}
Just get a list of the files with the _pn then process against the rest.
$pattern = "*_pn.mp3"
$files = Get-ChildItem -Path "$path" -File -filter "$pattern"
Foreach ($file in $files) {
$TestFN = $file.name -replace("_pn","")
If (Test-Path -Path $(Join-Path -Path $Path -ChildPath $TestFN)) {
$file | Remove-Item -force
}
} #End Foreach
Hello awesome community :)
I have a list containing a bunch of SKU's. All the filenames of the files, that I need to copy to a new location, starts with the corresponding SKU like so
B6BC004-022_10_300_f.jpg
In this case "B6BC004" is the SKU and my txt list contains "B6BC004" along with many other SKU's.
Somewhere in the code below I know I have to define that it should search for files beginning with the SKU's from the txt file but I have no idea how to define it.
Get-Content .\photostocopy.txt | Foreach-Object { copy-item -Path $_ -Destination "Z:\Photosdestination\"}
Thanks in advance :)
If all files start with one of the SKU's, followed by a dash like in your example, this should work:
$sourceFolder = 'ENTER THE PATH WHERE THE FILES TO COPY ARE'
$destination = 'Z:\Photosdestination'
# get an array of all SKU's
$sku = Get-Content .\photostocopy.txt | Select-Object -Unique
# loop through the list of files in the source folder and copy all that have a name beginning with one of the SKU's
Get-ChildItem -Path $sourceFolder -File -Recurse |
Where-Object { $sku -contains ($_.Name -split '\s*-')[0] } |
ForEach-Object { $_ | Copy-Item -Destination $destination }
I haven't tested this so please proceed with caution!
What is does it loops through all the items in your photostocopy.txt file, searches the $source location for a file(s) with a name like the current item from your file. It then checks if any were found before outputting something to the console and possibly moving the file(s).
$source = '#PATH_TO_SOURCE'
$destination = '#PATH_TO_DESTINATION'
$photosToCopy = Get-Content -Path '#PATH_TO_TXT_FILE'
$photosToCopy | ForEach-Object{
$filesToCopy = Get-ChildItem -Path $source -File | Where-Object {$_.Name -like "$_*"}
if ($fileToCopy.Count -le 0){
Write-Host "No files could be found for: " $_
}else{
$filesToCopy | ForEach-Object{
Write-Host "Moving: " $_.Name
Copy-Item -Path $_.FullName -Destination $destination
}
}
}
Let me know how if this helps you :)
I am working on creating a script that will read a .csv document containing a single column of filenames (one per cell) and search a larger folder for each of the files matching the filenames provided and identify the 'owner' using:
(get-acl $file).owner
Currently I have several bits of code that can do individual parts, but I am having a hard time tying it all together. Ideally, a user can simply input file names into the .csv file, then run the script to output a second .csv or .txt identifying each file name and it's owner.
csv formatting will appear as below (ASINs is header):
ASINs
B01M8N1D83.MAIN.PC_410
B01M14G0JV.MAIN.PC_410
Pull file names without header:
$images = Get-Content \\path\ASINs.csv | Select -skip 1
Find images in larger folder to pull full filename/path (not working):
ForEach($image in $images) {
$images.FullName | ForEach-Object
{
$ASIN | Get-ChildItem -Path $serverPath -Filter *.jpg -Recurse -ErrorAction SilentlyContinue -Force | Set-Content \\path\FullNames.csv
}
}
At that point I would like to use the full file paths provided by FullNames.csv to pull the owners from the files in their native location using the above mentioned:
(get-acl $file).owner
Does anyone have any ideas how to tie these together into one fluid script?
EDIT
I was able to get the following to work without the loop, reading one of the filenames, but I need it to loop as there are multiple filenames.
New CSV Format:
BaseName
B01LVVLSCM.MAIN.PC_410
B01LVY65AN.MAIN.PC_410
B01MAXORH6.MAIN.PC_410
B01MTGEMEE.MAIN.PC_410
New Script:
$desktopPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop)
$images = $desktopPath + '\Get_Owner'
Get-ChildItem -Path $images | Select BaseName | Export-Csv $desktopPath`\Filenames.csv -NoTypeInformation
$serverPath = 'C:\Users\tuggleg\Desktop\Archive'
$files = Import-Csv -Path $desktopPath`\Filenames.csv
While($true) {
ForEach ($fileName in $files.BaseName)
{
Get-ChildItem -Path $serverPath -Filter "*$fileName*" -Recurse -ErrorAction 'SilentlyContinue' |
Select-Object -Property #{
Name='Owner'
Expression={(Get-Acl -Path $_.FullName).Owner}
},'*' |
Export-Csv -Path $desktopPath`\Owners.csv -NoTypeInformation
}
}
Any ideas on the loop issue? Thanks everyone!
This example assumes your csv contains partial filenames. It will search the filepath and filter for those partials.
Example.csv
"ASINs"
"B01M8N1D83.MAIN.PC_410"
"B01M14G0JV.MAIN.PC_410"
Code.ps1
$Files = Import-Csv -Path '.\Example.csv'
ForEach ($FileName in $Files.ASINs)
{
Get-ChildItem -Path $serverPath -Filter "*$FileName*" -Recurse -ErrorAction 'SilentlyContinue' |
Select-Object -Property #{
Name='Owner'
Expression={(Get-Acl -Path $_.FullName).Owner}
},'*' |
Export-Csv -Path '\\path\FullNames.csv' -NoTypeInformation
}
I'm trying to build a script that I can use to delete old files based on Last Accessed date. As part of the script I want to interrogate each sub folder, find files not accessed in the last X days, create a log in the same folder of the files found and record file details in the log then delete the files.
What I think I need is a nested loop, loop 1 will get each subfolder (Get-ChildItem -Directory -Recurse) then for each folder found a second loop checks all files for last accessed date and if outside the limit will append the file details to a logfile in the folder (for user reference) and also to a master logfile (for IT Admin)
loop 1 is working as expected and getting the subfolders, but I cannot get the inner loop to recurse through the objects in the folder, I'm trying to use Get-ChildItem inside the first loop, is this the correct approach?
Code sample below, I have added pseudo to demo the logic, its really the loops I need help with:
# Set variables
$FolderPath = "E:TEST_G"
$ArchiveLimit = 7
$ArchiveDate = (Get-Date).AddDays(-$ArchiveLimit)
$MasterLogFile = "C:\Temp\ArchiveLog $(Get-Date -f yyyy-MM-dd).csv"
# Loop 1 - Iterate through each subfolder of $FolderPath
Get-ChildItem -Path $FolderPath -Directory -Recurse | ForEach-Object {
# Loop 2 - Check each file in the Subfolder and if Last Access is past
# $ArchiveDate take Action
Get-ChildItem -Path $_.DirectoryName | where {
$_.LastAccessTime -le $ArchiveDate
} | ForEach-Object {
# Check if FolderLogFile Exists, if not create it
# Append file details to folder Log
# Append File & Folder Details to Master Log
}
}
I think you're overcomplicating a bit:
#Set Variables
$FolderPath = "E:\TEST_G"
$ArchiveLimit = 7
$ArchiveDate = (Get-Date).AddDays(-$ArchiveLimit)
$MasterLogFile = "C:\Temp\ArchiveLog $(get-date -f yyyy-MM-dd).csv"
If (!(Test-Path $MasterLogFile)) {New-Item $MasterLogFile -Force}
Get-ChildItem -Path $FolderPath -File -Recurse |
Where-Object { $_.LastAccessTime -lt $ArchiveDate -and
$_.Extension -ne '.log' } |
ForEach-Object {
$FolderLogFile = Join-Path $_.DirectoryName 'name.log'
Add-Content -Value "details" -Path $FolderLogFile,$MasterLogFile
Try {
Remove-Item $_ -Force -EA Stop
} Catch {
Add-Content -Value "Unable to delete item! [$($_.Exception.GetType().FullName)] $($_.Exception.Message)"`
-Path $FolderLogFile,$MasterLogFile
}
}
Edit:
Multiple recursive loops are unnecessary since you're already taking a recursive action in the pipeline. It's powerful enough to do the processing without having to take extra action. Add-Content from the other answer is an excellent solution over Out-File as well, so I replaced mine.
One note, though, Add-Content's -Force flag does not create the folder structure like New-Item's will. That is the reason for the line under the $MasterLogFile declaration.
Your nested loop doesn't need recursion (the outer loop already takes care of that). Just process the files in each folder (make sure you exclude the folder log):
Get-ChildItem -Path $FolderPath -Directory -Recurse | ForEach-Object {
$FolderLogFile = Join-Path $_.DirectoryName 'FolderLog.log'
Get-ChildItem -Path $_.DirectoryName -File | Where-Object {
$_.LastAccessTime -le $ArchiveDate -and
$_.FullName -ne $FolderLogFile
} | ForEach-Object {
'file details' | Add-Content $FolderLogFile
'file and folder details' | Add-Content $MasterLogFile
Remove-Item $_.FullName -Force
}
}
You don't need to test for the existence of the folder log file, because Add-Content will automatically create it if it's missing.
I have this piece of powershell code below which creates an individual text file in the folder C:\Users\XX\Desktop\info\ from each individual zip file in the folder C:\Users\XX\Desktop\Powershell\Zip, with the name of the text files being the name of the zip files.
Get-ChildItem -Path "C:\Users\XX\Desktop\Powershell\Zip" -Recurse -exclude '*.info' | ForEach { [System.IO.File]::WriteAllText("C:\Users\XX\Desktop\info\"+ $_.Name + ".txt", $_.FullName)}
ontop of that I have the script below which gets the last modified date for the zip files
$path = 'C:\Users\XX\Desktop\Powershell\Zip'
$files = Get-ChildItem $path -Recurse -excluse '*.info'
foreach($file in $files){
$file.lastwritetime
and also this command that gets the computer name
{
(Get-WmiObject Win32_Computersystem).name
}
All these will be in one script, but I need the outputs of the 2nd and 3rd section of the script to append to the text file created in the first section of the script, appending to the appropriate file.
I have tried a couple of commands, the main one being [System.IO.File]::AppendAllText, but I cant seem to get anywhere with this.
Any ideas on the right way I should be doing this?
Thankyou.
You can try this :
$path = 'C:\Users\XX\Desktop\Powershell\Zip'
$files = Get-ChildItem $path -Recurse -Exclude '*.info'
$ComputerName = (Get-WmiObject Win32_Computersystem).name
foreach($file in $files) {
$OutputFilePath = "C:\Users\XX\Desktop\info\"+ $file.Name + ".txt"
[System.IO.File]::WriteAllText($OutputFilePath, $file.FullName)
$file.lastwritetime | Add-Content $OutputFilePath
$ComputerName | Add-Content $OutputFilePath
}