I have some powershell code that is meant to take specific characters out of file names recursively. This works great, unless taking the characters out causes 2 files to match names in the same folder.
I found the Powershell here PowerShell script to remove characters from files and folders but it doesn't solve the issue I am having.
$characters = "$#"
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse "D:\projects\filenameCleaner\TEST" | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru
}
I'm not very versed in Powershell yet and would love some help with this. I just want the script that, if it finds a duplicate, it should just add a (1) or (2) etc at the end, depending on how many there are.
Please don't just give me an answer, explain it so that I can learn what is happening
I think the way to tackle this is to create a new PSOBJECT which contains your source and target paths. You can then use the Group-object to group on the target path which will give you a count which you can then use to determine which files will need a number suffix.
Something like this:
Get-ChildItem -recurse "D:\projects\filenameCleaner\TEST" | ? {!$_.PsIscontainer} | % {
New-Object psobject -Property #{
source = $_.FullName
target = ($_.FullName -replace [regex]::Escape($_.Name)) + ($_.Name -replace '[\$#]+', '')
}
} | Group-Object target | % {
$g = $_.Group
0..($g.Count - 1) | % {
New-Object psobject -Property #{
source = $g[$_].source
target = $g[$_].target -replace '\.', #('.', "($($_)).")[$_ -gt 0]
}
}
} | % {Rename-Item -Path $_.source -NewName $_.Target}
The nice thing about doing it this way is that it can two, three or more duplcates
I have only done the code for files so you would need another version for folders.
I was able to do this by adding an if-statement that checks the new path
$characters = "$#}"
$regex = "[$([regex]::Escape($characters))]"
$path = "D:\projects\filenameCleaner\TEST"
$filesandfolders = Get-ChildItem -recurse $path | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
$newPath = $path+"\"+$New
$loop = 0
while (Test-Path $newPath) {
$loop = $loop + 1
$basename = $_.basename -Replace $regex
$New = $basename + " ("+$loop+")"+$_.extension
$newPath = $path+"\"+$New
}
"Rename `""+$_.name+"`" to `""+$New+"`""
Rename-Item -path $_.Fullname -newname $New -passthru
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
$newPath = $path+"\"+$New
$loop = 0
while (Test-Path $newPath) {
$loop = $loop + 1
$basename = $_.basename -Replace $regex
$New = $basename + " ("+$loop+")"+$_.extension
$newPath = $path+"\"+$New
}
"Rename `""+$_.name+"`" to `""+$New+"`""
Rename-Item -path $_.Fullname -newname $New -passthru
}
-- Another update that handles the extensions separately when adding the number to the string for duplicates. I was having issues that the number would go onto the file name after the extension which is obviously a no-go
Related
I need to sort words alphabetically from a specific file and put them into 26 text files named A.txt, B.txt and so on up to Z.txt.
$Content = Get-Content ".\\1.txt"
$Content = ($Content.Split(" .,:;?!/()\[\]{}-\`\`\`"")|sort)
$linecount = 0
$filenumber = 0
$destPath = "C:\\test"
$destFileSize = 26
$Content |Group {$_.Substring(0,1).ToUpper()} |ForEach-Object {
$path = Join-Path $destPath $_.Name
$\_.Group |Set-Content $path
}
$Content | % {
Add-Content $destPath$filenumber.txt "$\_"
$linecount++
If ($linecount -eq $destFileSize) {
$filenumber++
$linecount = 0
}
}
You could do something like this, but this also could mean some files may not be written if there are no words beginning with a certain letter found in the file:
$destPath = "D:\test"
(Get-Content -Path 'D:\Test\Lorem.txt' -Raw) -split '\W' -ne '' |
Group-Object {$_.Substring(0,1).ToUpperInvariant()} |
Where-Object {$_.Name -cmatch '[A-Z]'} | ForEach-Object {
$_.Group | Sort-Object | Set-Content -Path (Join-Path -Path $destPath -ChildPath ('{0}.txt' -f $_.Name))
}
If you always want exactly 26 files even if some may contain nothing, use this instead
$destPath = "D:\test"
$wordGroups = (Get-Content -Path 'D:\Test\Lorem.txt' -Raw) -split '\W' -ne '' |
Group-Object {$_.Substring(0,1).ToUpperInvariant()}
foreach ($char in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' -split '(.)' -ne '')) {
$outFile = Join-Path -Path $destPath -ChildPath ('{0}.txt' -f $char)
$group = $wordGroups | Where-Object { $_.Name -eq $char }
if ($group) { $group.Group | Sort-Object | Set-Content -Path $outFile } # output the found words
else { $null | Set-Content -Path $outFile } # or create an empty file
}
The Where-Object {$_.Name -cmatch '[A-Z]'} clause makes it ignore words starting with some other character than A to Z
I have created a script that will rename pst files to acl owner. It works. The only problem i have is when there are two pst files in the same folder.It gives them the same name. How can i add an increment in my script. It ried it with Si = 1 and Si++ but with no results.
Here is my script:
Get-ChildItem C:\Users\tester\* -Filter *.pst -recurse |
ForEach-Object{
$owner = ( $_ | Get-Acl ).Owner.Split("\")[1]
$newname = "$owner.pst"
$_ | Rename-Item -NewName $newname -Verbose -WhatIf
}
This is a quick and fast work around by adding names already used to an array and checking each time you loop.
$increment = 1
$alreadyProcessed = #()
Get-ChildItem C:\Users\tester\* -Filter *.pst -recurse |
ForEach-Object{
$owner = ( $_ | Get-Acl ).Owner.Split("\")[1]
$newname = "$owner.pst"
if($alreadyProcessed.Contains($newName))
{
$newName = "$owner`$increment.pst"
$increment++
}
$alreadyProcessed += $newname
$_ | Rename-Item -NewName $newname -Verbose -WhatIf
}
This will ensure a unique name each time.
If you plan to run your script multiple times in the same place then you should check to see what names are available otherwise your counter will reset to 1 but those files would already exist.
function Get-NextName ($file) {
$existing = (Get-Item "$file.*").Name
$i = 1
while ($existing -contains "$file.$i") {
$i++
}
"$file.$i"
}
And then adjust your function:
Get-ChildItem C:\Users\tester\* -Filter *.pst -recurse |
ForEach-Object{
$owner = ( $_ | Get-Acl ).Owner.Split("\")[1]
$newname = Get-NextName "$owner.pst"
$_ | Rename-Item -NewName $newname -Verbose -WhatIf
}
$getFiles = Get-ChildItem -Path $readDir -File -Include "*.doc","*.docx","*.xlsx"-Recurse | %{
if(($_ -match "\.doc$") -or ($_ -match "\.docx$")){
$Doc = $word.Documents.Open($_.fullname)
$nameDoc = $fileSaveLoc + $_.Name.Replace(".docx",".txt").replace(".doc",".txt")
$Doc.saveas([ref] $nameDoc, [ref] 5)
$Doc.close()
if((Get-ChildItem "I:\temp\").length -ne 0){
$locations = (Get-Item "I:\temp\"), (Get-ChildItem "I:\temp\" -Directory -recurse) | % {
Get-ChildItem -File $_.FullName | Select-String -List -Pattern '^\d{3}-?\d{2}-?\d{4}$' |
% Path
}
if($locations -ne $null){
$locations | out-file "I:\temp\SSN_FILES.txt"
#Get-ChildItem "I:\temp\" -exclude "fullpath.txt","SSN_FILES.txt" | remove-item
}else{
Get-ChildItem "I:\temp\" -exclude "fullpath.txt","SSN_FILES.txt" | remove-item
}
}
}
elseif($_ -match "\.xlsx$"){
$workbook = $excel.Workbooks.Open($_.FullName)
$csvFilePath = "I:\temp\" + $_.Name.Replace(".xlsx",".csv")
#$csvFilePath = $_.FullName -replace "\.xlsx$", ".csv"
$workbook.SaveAs($csvFilePath, [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)
$workbook.Close()
if((Get-ChildItem "I:\temp\").length -ne 0){
$locations = (Get-Item "I:\temp\"), (Get-ChildItem "I:\temp\" -Directory -recurse) | % {
Get-ChildItem -File $_.FullName | Select-String -List -Pattern '^\d{3}-?\d{2}-?\d{4}$' |
% Path
}
if($locations -ne $null){
$locations | out-file "I:\temp\SSN_FILES.txt"
#Get-ChildItem "I:\temp\" -exclude "fullpath.txt","SSN_FILES.txt" | remove-item
}else{
Get-ChildItem "I:\temp\" -exclude "fullpath.txt","SSN_FILES.txt" | remove-item
}
}
}
}
So this basically says:
check for a file matching doc/docx/xlsx
convert them into a file that can be parsed through
parse through each file at every iteration and compare it to a regex
if the regex is not null, then output it to a file with the file path
otherwise, delete it and anything else that was created except for two files
restart the process at the next file
Now the problem I am encountering is that the files aren't being deleted. I can't get them to be removed when they are created, when I know they don't match the regex. Setting ($locations -eq $true) doesn't solve that issue because it never goes into the first conditional statement.
The folder should contain only the two files that were created and possibly the ones that match the regex.
I have an issue that I cannot resolve no matter which way I am wrapping it up. I am including my latest code which is not giving me the desired outcome and the code for a solution that does work but for only one file at a time. I cannot work out how to loop through each of the files automatically however.
In a nutshell, I have a directory with many CSV files some of the entries within the CSVfile have a negative value (-) I need to remove this minus sign in all instances.
Now what works is if I use the following (on a single file)
$content = Get-Content "L:\Controls\BCR\0001cash.csv" | ForEach {$_ -replace $variable, ""} | Set-Content "L:\controls\bcr\0001temp.csv"
What I am trying to do is iterate through the many thousand of these objects automatically and not have to refer to them individually.
I started with:
$Directory = "L:\Controls\BCR\"
$variable = "-"
$suffix = ".tmp"
To define the directory, minus symbol that I want to remove and the suffix of the file I want to change to...
$Files = Get-ChildItem $Directory | Where-Object {$_.Extension -like "*csv*"} | Where-Object {$_.Name -like "*cash*"}
Is obtaining each of the files that I wish to work with
And I am then working with
ForEach ($File in $Files) { Get-Content $Files | ForEach {$_ -replace $variable, ""} | Set-Content {$_.Basename + $Variable}
The results however are nothing...
At a loss? Anyone???
$Directory = "L:\Controls\BCR\"
$variable = "-"
$suffix = ".tmp"
$Files = Get-ChildItem $Directory | Where-Object {$_.Extension -like "*csv*"} | Where-Object {$_.Name -like "*cash*"}
$process = ForEach ($File in $Files) { Get-Content $Files | ForEach {$_ -replace $variable, ""} | Set-Content {$_.BaseName + $suffix}
}
You are using the wrong variable in the Get-Content cmdlet ($Files instead of $File). Also You can simplify your script:
$Directory = "L:\Controls\BCR\"
$variable = "-"
$suffix = ".tmp"
Get-ChildItem $Directory -Filter '*cash*csv' |
ForEach-Object {
(Get-Content $_ -Raw) -replace $variable |
Set-Content {$_.BaseName + $suffix}
}
I am trying to
Create a CD_TMP file in each WE*.MS directory
Set content by processing the AHD*.TPL and ADT*.TPL files
Rename the AHD*.TPL to AHD*.TPL.Done and ADT*.TPL to AHD*.TPL.Done.
When there is only one WE.20150408.MS directory, the scripts works fine
but when there are more than one directories (i.e. WE.20150408.MS, WE.20151416.MS,WE.20140902.MS), it does not work and gives error message:
Get-Content: An object at specified path AHD*TPL does not exist of has been filtered by the -Include or -Exclude parameter.
At C:\Temp\Script\Script.ps1:24 Char:14
+ $content = Get=Content -path $AHD
+ CatagoryInfo :ObjectNotFound: (System.String[]:Strint[1) [Get-Content], Exception
+ FullyQualifiedErrorID: ItemNotFound,Micorsoft.Powershell.Commands.GetContentCommand
SCRIPT:
$SOURCE_DIR = "C:\Work"
$Work_DIR = "WE*MS"
$WE_DIR = "$SOURCE_DIR\$Work_DIR"
$AHD = "AHD*TPL"
$ADT = "ADT*TPL"
$AHD_FILES = $SOURCE_DIR
$CD_TMP = "CD_TMP"
$Str1 = "TEMP"
##############
Set-Location $WE_DIR
New-Item -Path "CD_TMP" -type file -force
#############
foreach ( $File in ( get-childitem -name $WE_DIR))
{
$content = Get-Content -path $AHD
$content | foreach {
If ($_.substring(0,4) -NotLike $Str1)
{
'0011' + '|' + 'HD' + '|' + 'AHD' + $_
}
} | Set-Content $CD_TMP
}
Get-ChildItem AHD*.TPL| ForEach {Move-Item $_ ($_.Name -replace ".TPL$",
".TPL.Done")}
##############
foreach ( $File in ( get-childitem -name $WE_DIR))
{
$content = Get-Content -path $ADT
$content | foreach {
If ($_.substring(0,4) -NotLike $Str1)
{
'0022' + '|' + 'DT' + '|' + 'ADT' + $_
}
} | Set-Content $CD_TMP
}
Get-ChildItem ADT*TPL| ForEach {Move-Item $_ ($_.Name -replace ".TPL$",
".TPL.Done")}
PAUSE
Is it first giving the error Set-Location : Cannot set the location because path 'C:\Work\WE*MS' resolved to multiple containers. ? That's what I expect it to say when it fails.
Then, because it can't change into the folder, it can't find any AHD files.
Does it work properly for one folder? It writes the CD_TMP file for AHD files, then overwrites it for ADT files. That doesn't seem right.
Also you can make it a bit more direct by changing:
putting lots of things in $CAPITAL variables at the start, then using them once, or never.
The .substring() -notlike test to use .startswith()
The string building with ++++ into a single string
The renaming into a Rename-Item with -NewName scriptblock
I'm thinking this:
$folders = Get-ChildItem "C:\Work\WE*MS" -Directory
foreach ($folder in $folders) {
# AHD files
$content = Get-Content "$folder\AHD*.TPL"
$content = $content | where { -not $_.StartsWith('TEMP') }
$content | foreach {"0011|HD|AHD$_"} | Set-Content "$folder\CD_TMP" -Force
Get-ChildItem "$folder\AHD*.TPL" | Rename-Item -NewName {$_.Name + '.Done'}
# ADT files
$content = Get-Content "$folder\ADT*.TPL"
$content = $content | where { -not $_.StartsWith('TEMP') }
$content | foreach {"0011|HD|ADT$_"} | Add-Content "$folder\CD_TMP"
Get-ChildItem "$folder\ADT*.TPL" | Rename-Item -NewName {$_.Name + '.Done'}
}
Although I don't know what the input or output should be, so I can't test it. NB. it now does Add-Content to append to the CD_TMP file, instead of overwriting it.
There's still alot of redundancy with $content, but the lines mostly stand alone like this.