Basically, I have a CSV file with a lot of columns. Let's say it looks like this:
_username, platform_
username1, platformX1
username2, platformY
username3, platformX2
username4, platformX2
..., ...
I would like to write a script that goes through the file and for each platform, it creates a file with the specific username in a different folder, so I would have:
\platformX1\username1.file
\platformY\username2.file
\platformX2\username3.file, username4.file
etc etc...
I know I should use foreach with an if placed somewhere, but powershell is new for me, and I don't really know how to start it.
Here's something similar tweaked to what you want to do.
This created files of a specific size in your directory
$data = get-content "C:\Data\masteFile.csv"
$drcty = "C:\Data"
foreach($line in $data)
{
$a,$b = $line.Split("{,}")
$parent = $drcty+"\"+$a+"\"
$filename = $parent + $b.TRIM() +".txt"
write-host $filename
New-Item -ItemType directory -Path $parent
fsutil file createnew $filename 2000
}
this seems to do what you want. [grin]
# fake reading in a CSV file
# in real life, use Import-CSV
$InStuff = #'
_UserName, Platform_
username1, platformX1
username2, platformY
username3, platformX2
username4, platformX2
'# | ConvertFrom-Csv
$DestDir = $env:TEMP
$Extension = 'txt'
foreach ($IS_Item in $InStuff)
{
$TargetPath = Join-Path -Path $DestDir -ChildPath $IS_Item.Platform_
if (-not (Test-Path -LiteralPath $TargetPath))
{
# "$Null = " will supress unwanted output from New-Item
$Null = New-Item -Path $TargetPath -ItemType Directory
}
$FileName = $IS_Item._UserName, $Extension -join '.'
$FullFileName = Join-Path -Path $TargetPath -ChildPath $FileName
if (-not (Test-Path -LiteralPath $FullFileName))
{
# "$Null = " will supress unwanted output from New-Item
$Null = New-Item -Path $FullFileName -ItemType File
}
}
i think what it does is apparent, but i know i get ahead of where i otta be at times. so, if there are any questions, please feel free to ask ... [grin]
This worked for me.
$ErrorActionPreference = "Stop"
#Path of the CSV file resides
$csvData = Import-Csv -Path "$env:USERPROFILE\Desktop\data.csv"
$DestinationFolder = "C:\Stuffs"
foreach($record in $csvData)
{
$destPlatformFolder = $DestinationFolder + "\" + $record.platform_
if(-not(Test-Path -Path $destPlatformFolder)){
New-Item -Path $destPlatformFolder -ItemType Directory -Force | Out-Null
}
$destinationFile = $destPlatformFolder + "\" + $record._username
New-Item -Path $destinationFile -ItemType File -Force | Out-Null
}
Related
Renaming files from a CSV
I am trying to write a script to reorganize files in a folder structure using csv as index file but I canĀ“t figure out in how to solve the Rename-Item error.
Questions
Is there others way to write this script in order to achieve the same results more easily?
How to pass the right parameters to Rename-Item?
My csv file template
folderName newName oldName
---------- ------- -------
01 Course Overview 01_Course_Overview 1280x720.mp4
02 Introduction to PowerShell 01_Introduction to PowerShell 1280x720 (1).mp4
02 Introduction to PowerShell 02_Who Is This Course For? 1280x720 (2).mp4
02 Introduction to PowerShell 03_What Is PowerShell? 1280x720 (3).mp4
02 Introduction to PowerShell 04_Windows PowerShell and PowerShell 7 1280x720 (4).mp4
PowerShell Script
$csv = Import-Csv '.\index.csv' -Delimiter ';'
$newFolders = $csv.folderName | Sort-Object -Unique
$listFolders = Get-ChildItem -Directory | Select-Object Name
$listFiles = Get-ChildItem | Where {$_.extension -eq ".mp4"}
ForEach ($a in $newFolders){
If ($listFolders.Name -contains $a){
Write-Host "The Folder $a exist"
}
else{
New-Item -Path $pwd.Path -Name $a -Type Directory | Out-Null
Write-Host "The folder $a has been created"
}
}
ForEach ($b in $csv){
If ($listFiles.Name -contains $b.oldName){
Write-Host "File $($b.oldName) exist"
Write-Host "Renaming file to: "$($b.newName)"
#Rename-Item $($b.oldName) -NewName $($b.newName)
#Write-Host "Moving file to: "$($b.folderName)"
#Move-Item .\$($b.newName) -Destination .\$($b.folderName)
}
else{
Write-Host "File $($b.oldName) doesn't exist" `n
}
}
Error when executin Rename-Item
No D:\Downloads\Pluralsight\_PowerShell_Essentials\01_Powershell_Getting_Started\Temp\indexfiles.ps1:30 caractere:9
+ Rename-Item $($b.oldName) -NewName $($b.newName)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (D:\Downloads\Pl...280x720 (2).mp4:String) [Rename-Item], ArgumentException
+ FullyQualifiedErrorId : RenameItemArgumentError,Microsoft.PowerShell.Commands.RenameItemCommand
Here you have an example of how this can be done, this is mainly a test case, it will create the files as you show us on the CSV and move them to the new folders based on the folderName column.
The code will look for the files on the current directory, before testing it with the real files, Set-Location (cd) to that folder.
If you're not sure if the code will work you can add a -WhatIf switch to Rename-Item and Move-Item.
Note, I have removed ? from the newName column since it's an invalid character on Windows. See this answer for more details.
# Go to a temporary folder for testing
Set-Location path/to/temporaryfolder/here
# Here you would use:
# $csv = Import-Csv path/to/csv.csv
$csv = #'
folderName newName oldName
01 Course Overview 01_Course_Overview 1280x720.mp4
02 Introduction to PowerShell 01_Introduction to PowerShell 1280x720 (1).mp4
02 Introduction to PowerShell 02_Who Is This Course For 1280x720 (2).mp4
02 Introduction to PowerShell 03_What Is PowerShell 1280x720 (3).mp4
02 Introduction to PowerShell 04_Windows PowerShell and PowerShell 7 1280x720 (4).mp4
'# -replace ' +',',' | ConvertFrom-Csv
# Create test files, this part is only for testing the code
$csv.foreach({ New-Item $_.oldName -ItemType File })
foreach($line in $csv)
{
if(-not (Test-Path $line.folderName))
{
# Create the Folder if it does not exist
New-Item $line.folderName -ItemType Directory -Verbose
}
Rename-Item -LiteralPath $line.oldName -NewName $line.newName
Move-Item -LiteralPath $line.newName -Destination $line.folderName
}
If I understand correctly, your real CSV file contains folder and/or file names with characters that are invalid like the ?.
To fix that, you can choose to remove those characters from the CSV file first, OR make sure you remove them before creating a folder or renaming a file.
For both options, you can use this small helper function:
function Remove-InvalidNameChars {
param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[String]$Name,
[ValidateSet('File', 'Path')]
[string]$Type ='File'
)
if ($Type -eq 'File') {
$invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
}
else {
$invalidChars = [IO.Path]::GetInvalidPathChars() -join ''
}
# build a regex string from the invalid characters
$removeThese = "[{0}]" -f [RegEx]::Escape($invalidChars)
# output the name with invalid characters removed
$Name -replace $removeThese
}
Method 1: remove the invalid characters from the CSV file and use cleaned-up data:
$sourcePath = 'D:\Test'
$csvFile = Join-Path -Path $sourcePath -ChildPath 'index.csv'
$csvData = Import-Csv -Path $csvFile -Delimiter ';'
foreach ($item in $csvData) {
$item.folderName = Remove-InvalidNameChars -Name $item.folderName -Type Path
$item.newName = Remove-InvalidNameChars -Name $item.newName -Type File
}
$csvData | Export-Csv -Path $csvFile -Delimiter ';' -Force # rewrite the CSV file if you like
# now use the cleaned-up data in $csvData for the rest of the code:
foreach ($item in $csvData) {
# create the output folder if this does not already exist
$targetPath = Join-Path -Path $sourcePath -ChildPath $item.folderName
$null = New-Item -Path $targetPath -ItemType Directory -Force
# move and rename the file if found
$sourceFile = Join-Path -Path $sourcePath -ChildPath $item.oldName
if (Test-Path -Path $sourceFile -PathType Leaf) {
$targetFile = Join-Path -Path $targetPath -ChildPath $item.newName
Move-Item -Path $sourceFile -Destination $targetFile
}
}
Method 2: leave the csv data as-is and make sure you remove invalid characters while renaming/moving:
$sourcePath = 'D:\Test'
$csvFile = Join-Path -Path $sourcePath -ChildPath 'index.csv'
$csvData = Import-Csv -Path $csvFile -Delimiter ';'
foreach ($item in $csvData) {
# create the output folder if this does not already exist
$targetPath = Join-Path -Path $sourcePath -ChildPath (Remove-InvalidNameChars -Name $item.folderName -Type Path)
$null = New-Item -Path $targetPath -ItemType Directory -Force
# move and rename the file if found
$sourceFile = Join-Path -Path $sourcePath -ChildPath (Remove-InvalidNameChars -Name $item.oldName -Type File)
if (Test-Path -Path $sourceFile -PathType Leaf) {
$targetFile = Join-Path -Path $targetPath -ChildPath (Remove-InvalidNameChars -Name $item.newName -Type File)
Move-Item -Path $sourceFile -Destination $targetFile
}
}
Note that Move-Item can move a file to a new destination and rename it at the same time, so you do not need Rename-Item
P.S. I noticed in your example CSV there are no extensions to the newName filenames..
If that is the case in real life, you need to add these aswell.
For that change the Move-Item line to:
Move-Item -Path $sourceFile -Destination ([IO.Path]::ChangeExtension($targetFile, [IO.Path]::GetExtension($sourceFile)))
I would like to unzip some files each into their own folder with the same name as the zip file. I have been doing clunky things like this, but as this is PowerShell, there is usually a much smarter way to achieve things.
Are there some kind of one-or-two-liner ways that I can operate on each zip file in a folder and extract it into a subfolder of the same name as the zip (but without the extension)?
foreach ($i in $zipfiles) {
$src = $i.FullName
$name = $i.Name
$ext = $i.Extension
$name_noext = ($name -split $ext)[0]
$out = Split-Path $src
$dst = Join-Path $out $name_noext
$info += "`n`n$name`n==========`n"
if (!(Test-Path $dst)) {
New-Item -Type Directory $dst -EA Silent | Out-Null
Expand-Archive -LiteralPath $src -DestinationPath $dst -EA Silent | Out-Null
}
}
You could do with a few less variables. When the $zipfiles collection contains FileInfo objects as it appears, most variables can be replaced by using the properties the objects already have.
Also, try to avoid concatenating to a variable with += because that is both time and memory consuming.
Just capture the result of whatever you output in the loop in a variable.
Something like this:
# capture the stuff you want here as array
$info = foreach ($zip in $zipfiles) {
# output whatever you need to be collected in $info
$zip.Name
# construct the folderpath for the unzipped files
$dst = Join-Path -Path $zip.DirectoryName -ChildPath $zip.BaseName
if (!(Test-Path $dst -PathType Container)) {
$null = New-Item -ItemType Directory $dst -ErrorAction SilentlyContinue
$null = Expand-Archive -LiteralPath $zip.FullName -DestinationPath $dst -ErrorAction SilentlyContinue
}
}
# now you can create a multiline string from the $info array
$result = $info -join "`r`n==========`r`n"
So far I have tried the following script:
$SourceFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\"
$TargetFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\Final\"
Get-ChildItem -Path $SourceFolder -Filter *.pdf |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.pdf','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
if( -not ( Test-Path -Path $Destination.Directory.FullName )){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
This creates a folder for every pdf in the folder.
I need it create a single folder based on the 5 digit in the name and move those files into the new folder.
For example: I could have 10 pdf's that have the number "30565" in them and the new folder should be named "30565"
Here are some file names to explain:
LKY_20974_Pr01_1-5000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr02_5001-10000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr03_10001-15000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
I have tried to include an else block to the best answer script and haven't had much success. I did however create a separate script that will archive the files before creating a new file. I just have to run it before the main powershell script.
$SourceDir = 'D:\WORK\JetLetter\LKY\LKY_jV_004_9835'
$DestDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files'
$ArchiveDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files\#archive'
$Filter = '*.pdf'
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (Test-Path -LiteralPath $FullTargetDir)
{
# the "$Null =" is to suppress unwanted output about what was done
$null = Move-Item -Path $FullTargetDir -Destination $ArchiveDir -Force
}
}
This has made the files and folders a lot more organized.
i think this does what you want done. [grin] the comments seem adequate, but if you have any questions, please ask.
$SourceDir = 'c:\temp\JetLetter\LKY\LKY_jv_004'
$DestDir = 'c:\temp\JetLetter\LKY\LKY_jv_004\Final'
$Filter = '*.pdf'
#region >>> make the dirs and sample files to work with
# remove the entire "#region/#endregion" block when you are ready to work with real data
# make the dirs
$Null = mkdir -Path $SourceDir, $DestDir -ErrorAction 'SilentlyContinue'
# make the test files
$SampleFiles = #(
'LKY_11111_Pr11_1-11111.pdf'
'LKY_22222_Pr22_2-22222.pdf'
'LKY_22222_Pr22_2222-2222.pdf'
'LKY_33333_Pr33_3-3333.pdf'
'LKY_33333_Pr33_33333-33333.pdf'
'LKY_55555_Pr55_5-5555.pdf'
'LKY_77777_Pr77_7-77777.pdf'
'LKY_77777_Pr77_77777-77777.pdf'
'LKY_99999_Pr99_9-99999.pdf'
)
foreach ($SF_Item in $SampleFiles)
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $SourceDir -Name $SF_Item -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#endregion >>> make the dirs and sample files to work with
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (-not (Test-Path -LiteralPath $FullTargetDir))
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $FullTargetDir -ItemType 'Directory'
}
$NewFullFileName = Join-Path -Path $FullTargetDir -ChildPath $FL_Item.Name
# leave the file in the source dir if it already is in the final target dir
# you may want to save the not-copied info to a file for later review
if (-not (Test-Path -LiteralPath $NewFullFileName))
{
# the "Move-Item" cmdlet on win7ps5.1 is wildly unreliable
# so i used copy & then remove
$Null = Copy-Item -LiteralPath $FL_Item.FullName -Destination $NewFullFileName
Remove-Item -LiteralPath $FL_Item.FullName
}
else
{
Write-Warning (' {0} already exists in {1}' -f $FL_Item.Name, $FullTargetDir)
Write-Warning ' The file was not moved.'
Write-Warning ''
}
}
screen output only exists for "not moved" files. again, you may want to save the list to a $Var or to a file for later work.
one of the moved files ...
C:\Temp\JetLetter\LKY\LKY_jv_004\Final\22222\LKY_22222_Pr22_2222-2222.pdf
I am trying to move thousands of files into YYYY-MM folders, using a scheduled task which runs every night. The files are currently stored here:
C:\Users\USER\Desktop\New folder
I want them to be stored like this:
C:\Users\USER\Desktop\New folder\YYYY\MM\
eg:
C:\Users\USER\Desktop\New folder\2019\05
The script would create the YYYY\MM folders if they dont already exist. The file name is in the following format:
Status_20200116_001058.txt
So for the above file, YYYY is 2020 and the MM is 01
I found a PS script on Stack and amended it to the below, but I have not used PS much before and am a bit lost:
$Files_Folder = "C:\Users\USER\Desktop\New folder"
get-childitem | % {
$file = $_.FullName
$month = $date.month
$year = $date.year
new-item -type Directory -path "$Files_Folder\$year\$month"
move-item $file "$Files_Folder\$year\$month"
}
Running the above results in PS creating a whole bunch of folders and files in the location above, which I cannot delete:
Any help would be most appreciated
Edit: So I tried the script from #Wasif Hasan like this:
$Files_Folder = "C:\Users\USER\Desktop\New folder1"
get-childitem | % {
$file = $_.FullName
$month = $file.substring(12, 2)
$year = $file.substring(8, 4)
$folder = Join-Path -Path "$($Files_Folder)" -ChildPath "$($Year)"
$folder = Join-Path -Path "$($folder)" -ChildPath "$($Month)"
$Exists = Test-Path "$($Folder)"
If (!$Exists) { New-Item -Type directory -Path "$($Folder)" }
Move-Item "$($File)" "$($Folder)"
}
However I just get a bunch of errors in PS:
Edit: Just tried the script by #Pavithran G, amended for using a Year and Month variable instead of Month and Date:
$loc = "C:\Users\nazadmin\Desktop\New folder1"
$files = Get-ChildItem -Path $loc
for ($i=0; $i -lt $files.Count; $i++)
{
$outfile = $files[$i].FullName
$filename = Split-Path -Path $outfile -Leaf -Resolve
$Year = $filename -replace "Status_\d{0}(\d{4})[\d_]*.txt",'$1'
$Month = $filename -replace "Status_\d{4}(\d{2})[\d_]*.txt",'$1'
$folder = Join-Path -Path $loc -ChildPath $Year
$folder = Join-Path -Path $folder -ChildPath $Month
$Exists = Test-Path $folder
If (!$Exists)
{
New-Item -Type directory -Path $folder
}
Move-Item $outfile $folder
}
This works perfectly!
Edit: Just realised that if the YY folder exists already, then the script tries to create another YY folder within the YY folder, and throws an error:
Move-Item : Access to the path 'C:\Users\USER\Desktop\New folder1\2018' is denied.
At line:14 char:10
+ Move-Item <<<< $outfile $folder
+ CategoryInfo : WriteError: (C:\Users\USER...ew folder1\2018:DirectoryInfo) [Move-Item], IOException
+ FullyQualifiedErrorId : MoveDirectoryItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand
Any way I can avoid this situation?
Edit: Will post a new question to make it clearer
Cheers
Below script will create folder based on the filename present in that.So,no need to add for scheduled task, you can run at any time , it will automatically create folder,but don't change the format of the filename which you mentioned earlier.
$loc = "C:\Users\USER\Desktop\New folder"
$files = Get-ChildItem -Path $loc
for ($i=0; $i -lt $files.Count; $i++)
{
$outfile = $files[$i].FullName
$filename = Split-Path -Path $outfile -Leaf -Resolve
$Month = $filename -replace "Status_\d{4}(\d{2})[\d_]*.txt",'$1'
$date = $filename -replace "Status_\d{6}(\d{2})[\d_]*.txt",'$1'
$folder = Join-Path -Path $loc -ChildPath $Month
$folder = Join-Path -Path $folder -ChildPath $date
$Exists = Test-Path $folder
If (!$Exists)
{
New-Item -Type directory -Path $folder
}
Move-Item $outfile $folder
}
Make sure ,run the Powershell script in Admin mode because sometimes it need administrator access to move or copy file
$date is not declared as a date variable (Get-Date). Use Join-Path to safely create paths and you have not used Test-Path check the folder existence. Use this:
$date = Get-Date
$Files_Folder = "C:\Users\USER\Desktop\New folder"
get-childitem | % {
$file = $_.FullName
$month = $date.month
$year = $date.year
$folder = Join-Path -Path "$($Files_Folder)" -ChildPath "$($Year)"
$folder = Join-Path -Path "$($folder)" -ChildPath "$($Month)"
$Exists = Test-Path "$($Folder)"
If (!$Exists) { New-Item -Type directory -Path "$($Folder)" }
Move-Item "$($File)" "$($Folder)"
}
I have a script that reorganizes files in C:\Year\Month\"StoreFiles" to "C:\Store\Date\"StoreFiles" based on modified date.
#Variables
$StoreName = "aStore"
$SourceDir = "C:\Source"
$TargetDir = "C:\$StoreName"
# Create Folder based on Store List
if(-Not (Test-Path -Path $TargetDir)) {
New-Item -ItemType Directory -Path $TargetDir
}
# Search & create folders based on date
Get-ChildItem "$SourceDir\*$StoreName*" -Recurse | ForEach-Object {
$x = $_.LastWriteTime.ToShortDateString()
$new_folder_name = Get-Date $x -Format MMM-yyyy
$des_path = "$TargetDir\$new_folder_name"
if (Test-Path $des_path) {
Copy-Item $_.FullName $des_path
} else {
New-Item -ItemType Directory -Path $des_path
Copy-Item $_.FullName $des_path
}
}
Works great however the modified date isn't ideal. Can I use the original folder path of "C:\Year\Month\"StoreFiles" and use it as dates for the new output?
In a sense I am trying to do the following:
"C:\Year\Month\"StoreFiles" to "C:\Store\Month-Year\"StoreFiles"
Maybe I should assign the folder path as a variable and use it accordingly for the new output?
Or is there another way I should think about doing this.
try Something like this
$dirroot="C:\temp\Root"
$store="C:\store"
Get-ChildItem $dirroot -file -Recurse |
where FullName -match ([Regex]::Escape($dirroot) + "\\\d{4}\\\d{1,2}\\" + [Regex]::Escape($_.Name)) |
% {
$elements=$_.FullName.Replace($dirroot, '').Split('\')
New-Item -ItemType Directory -Path "$store\$($elements[2])-$($elements[1])" -Force
Copy-Item $_.FullName "$store\$($elements[2])-$($elements[1])\$($elements[3])"
}
Cann't you use something like this?
$DateInfo = '' | select-object -Property 'Year', 'Month'
$File = 'c:\2014\may\filename.txt'
$TopFolderpath = [io.Path]::GetDirectoryName($File)
$TopFolderName = [io.Path]::GetFileName($TopFolderpath)
$DateInfo.Month = $TopFolderName
$Subfolderpath = [io.Path]::GetDirectoryName($TopFolderpath)
$SubFolderName = [io.Path]::GetFileName($Subfolderpath)
$DateInfo.Year = $SubFolderName
$DateInfo