I am busy with a windows powershell script to batch rename a bunch of folders with IDs, which works perfectly fine. My question is how do i add validation to ensure all the IDs are unique when all my files get renamed. Here is the code:
param(
[Parameter(Mandatory=$true)]
[int]$idx
)
Get-ChildItem *.sql | Foreach-Object {
$iPrefix = ('{0:d5}' -f $idx)
$path = (Split-Path -Path($_))
$filename = (Split-Path -Path($_) -Leaf) -replace "\[|\]",""
#%{ write-host $path}
%{ write-host $filename}
if(!$filename.StartsWith("script","CurrentCultureIgnoreCase"))
{
#%{ write-host "Script$iPrefix - $filename"}
Rename-Item -LiteralPath(($_)) -NewName("Script$iPrefix - $filename")
++$idx
%{ write-host "Renamed: " + $filename}
}
}
Here is a screenshot of what i want to avoid:
As you can see Script02185 is repeated twice, because the script was ran at two different times. How do i ensure that the numbers will remain unique?
Try this.
$files = Get-ChildItem . -Filter *.sql
$prefixedFiles, $unprefixedFiles = $files.Where({ $_.Name -match "^Script\d{5} - " }, 'split')
$usedIDs = [int[]]$prefixedFiles.Name.Substring(6, 5)
$unusedIDs = [System.Collections.Generic.HashSet[int]](1..99999)
$unusedIDs.ExceptWith($usedIDs)
$enumerator = $unusedIDs.GetEnumerator()
$unprefixedFiles | Rename-Item -NewName {
if (!$enumerator.MoveNext()) { throw "`nThere are no unused IDs." }
"Script{0:D5} - {1}" -f $enumerator.Current, ($_.Name -replace '\[|\]')
} -ErrorAction Stop -PassThru
Related
I am trying to construct a script that moves through specific folders and the log files in it, and filters the error codes. After that it passes them into a new file.
I'm not really sure how to do that with for loops so I'll leave my code bellow.
If someone could tell me what I'm doing wrong, that would be greatly appreciated.
$file_name = Read-Host -Prompt 'Name of the new file: '
$path = 'C:\Users\user\Power\log_script\logs'
Add-Type -AssemblyName System.IO.Compression.FileSystem
function Unzip
{
param([string]$zipfile, [string]$outpath)
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
}
if ([System.IO.File]::Exists($path)) {
Remove-Item $path
Unzip 'C:\Users\user\Power\log_script\logs.zip' 'C:\Users\user\Power\log_script'
} else {
Unzip 'C:\Users\user\Power\log_script\logs.zip' 'C:\Users\user\Power\log_script'
}
$folder = Get-ChildItem -Path 'C:\Users\user\Power\log_script\logs\LogFiles'
$files = foreach($logfolder in $folder) {
$content = foreach($line in $files) {
if ($line -match '([ ][4-5][0-5][0-9][ ])') {
echo $line
}
}
}
$content | Out-File $file_name -Force -Encoding ascii
Inside the LogFiles folder are three more folders each containing log files.
Thanks
Expanding on a comment above about recursing the folder structure, and then actually retrieving the content of the files, you could try something line this:
$allFiles = Get-ChildItem -Path 'C:\Users\user\Power\log_script\logs\LogFiles' -Recurse
# iterate the files
$allFiles | ForEach-Object {
# iterate the content of each file, line by line
Get-Content $_ | ForEach-Object {
if ($_ -match '([ ][4-5][0-5][0-9][ ])') {
echo $_
}
}
}
It looks like your inner loop is of a collection ($files) that doesn't yet exist. You assign $files to the output of a ForEach(...) loop then try to nest another loop of $files inside it. Of course at this point $files isn't available to be looped.
Regardless, the issue is you are never reading the content of your log files. Even if you managed to loop through the output of Get-ChildItem, you need to look at each line to perform the match.
Obviously I cannot completely test this, but I see a few issues and have rewritten as below:
$file_name = Read-Host -Prompt 'Name of the new file'
$path = 'C:\Users\user\Power\log_script\logs'
$Pattern = '([ ][4-5][0-5][0-9][ ])'
if ( [System.IO.File]::Exists( $path ) ) { Remove-Item $path }
Expand-Archive 'C:\Users\user\Power\log_script\logs.zip' 'C:\Users\user\Power\log_script'
Select-String -Path 'C:\Users\user\Power\log_script\logs\LogFiles\*' -Pattern $Pattern |
Select-Object -ExpandProperty line |
Out-File $file_name -Force -Encoding ascii
Note: Select-String cannot recurse on its own.
I'm not sure you need to write your own UnZip function. PowerShell has the Expand-Archive cmdlet which can at least match the functionality thus far:
Expand-Archive -Path <SourceZipPath> -DestinationPath <DestinationFolder>
Note: The -Force parameter allows it to over write the destination files if they are already present. which may be a substitute for testing if the file exists and deleting if it does.
If you are going to test for the file that section of code can be simplified as:
if ( [System.IO.File]::Exists( $path ) ) { Remove-Item $path }
Unzip 'C:\Users\user\Power\log_script\logs.zip' 'C:\Users\user\Power\log_script'
This is because you were going to run the UnZip command regardless...
Note: You could also use Test-Path for this.
Also there are enumerable ways to get the matching lines, here are a couple of extra samples:
Get-ChildItem -Path 'C:\Users\user\Power\log_script\logs\LogFiles' |
ForEach-Object{
( Get-Content $_.FullName ) -match $Pattern
# Using match in this way will echo the lines that matched from each run of
# Get-Content. If nothing matched nothing will output on that iteration.
} |
Out-File $file_name -Force -Encoding ascii
This approach will read the entire file into an array before running the match on it. For large files it may pose a memory issue, however it enabled the clever use of -match.
OR:
Get-ChildItem -Path 'C:\Users\user\Power\log_script\logs\LogFiles' |
Get-Content |
ForEach-Object{ If( $_ -match $Pattern ) { $_ } } |
Out-File $file_name -Force -Encoding ascii
Note: You don't need the alias echo or its real cmdlet Write-Output
UPDATE: After fuzzing around a bit and trying different things I finally got it to work.
I'll include the code below just for demonstration purposes.
Thanks everyone
$start = Get-Date
"`n$start`n"
$file_name = Read-Host -Prompt 'Name of the new file: '
Out-File $file_name -Force -Encoding ascii
Expand-Archive -Path 'C:\Users\User\Power\log_script\logs.zip' -Force
$i = 1
$folders = Get-ChildItem -Path 'C:\Users\User\Power\log_script\logs\logs\LogFiles' -Name -Recurse -Include *.log
foreach($item in $folders) {
$files = 'C:\Users\User\Power\log_script\logs\logs\LogFiles\' + $item
foreach($file in $files){
$content = Get-Content $file
Write-Progress -Activity "Filtering..." -Status "File $i of $($folders.Count)" -PercentComplete (($i / $folders.Count) * 100)
$i++
$output = foreach($line in $content) {
if ($line -match '([ ][4-5][0-5][0-9][ ])') {
Add-Content -Path $file_name -Value $line
}
}
}
}
$end = Get-Date
$time = [int]($end - $start).TotalSeconds
Write-Output ("Runtime: " + $time + " Seconds" -join ' ')
Overview
Hi StackOverflow!
I'm hoping someone on here could help me.
I have built the following code to append text to the end of filename on a network drive.
The files in question are primarily ".zip" files and usually range from 6 - 8 sub-directories in the parent folder with 8 - 10 ".zip" files per sub-directoy.
What I am hoping to achieve is to append text to the end of each filename recursively.
Code:
#Current Directory of creatives
$fileLocation = read-host "Type/Paste location of creatives"
if (Test-Path \\serverPath\name\* -PathType Leaf) {
$serverPathName = "\\serverPath\name\"
$driveLetter = "D:\"
$fileLocation = ($fileLocation -replace [regex]::Escape($serverPathName),$driveLetter)
}
$fileLocation = Resolve-Path $fileLocation
Write-Output $fileLocation
$newText = read-host "Please enter text to append"
$newText = $newText -replace '\s','-'
$addMarket = read-host "Are folders split by market? [Y/N]"
$ZipFiles = Get-ChildItem -Path "$currentDirectory" -Recurse -Filter "*.zip"
if ($addMarket -eq "N") {
foreach ($Zipfile in $ZipFiles) {
Get-ChildItem | %{$_|rename-item -NewName ((($_.BaseName -replace '\s','-') + "-" + $newText + $_.Extension))}
}
}
elseif($addMarket -eq "Y") {
foreach ($Zipfile in $ZipFiles) {
Get-ChildItem -File -Recurse | Rename-Item -NewName {(($_.BaseName -replace '\s','-') + "-" + (($_.Directory.Name -replace '\s','-')) + "-" + $newText + $_.Extension).ToLower()}
}
}
else {
write-host "ERROR! Incorrect Input!"
Write-Host "Exiting Script..."
Exit
}
#Clear Console
clear
#Read-Host -Prompt “Press Enter to exit”
Current Situation:
When I run the above (without the loop function), it works fine but I have to into each sub-directory and run the script directly there - which is obviously time-consuming and monotonous.
When I run it within the loop, it works but loops for a long time until the total count of ".zip" files have been reached. e.g. 8 sub-dirs, 9 ".zip" = loops 72 times - each time appending the same text.
Expected Situation:
I wish to run the script from the parent folder of the sub-dirs and the script will only append the text once per sub-dir to all ".zip" and then exit the script.
Problem(?)
I believe the issue lies in the following variable:
$ZipFiles
But I am unable to figure out how to rectify this. I've set it to find all ".zip" files in the parent folder. I've also set it to count he number of files per sub-dir:
Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_ | Measure-Object).Count}
But neither has worked for me.
Summary:
I'm hoping someone can point out to me the error I am making and where the fix needs to be.
I am open to all suggestions. If there is a better approach, please also let me know.
I don't mind changing how the code works, if it means the same end result.
Thanks for reading!
Rajiv Ahmed
You have some errors/mistakes in your code:
- You ask to provide a $fileLocation but you don't use it in your code.
- Instead you use a variable $currentDirectory but that's not defined.
- Then you collect all zip files in a variable $ZipFiles, you iterate over this array where you already have zip files but you use Get-ChildItem to get them again. ;-)
Something like this should work actually:
$fileLocation = read-host "Type/Paste location of creatives"
if (Test-Path \\serverPath\name\* -PathType Leaf) {
$serverPathName = "\\serverPath\name\"
$driveLetter = "D:\"
$fileLocation = ($fileLocation -replace [regex]::Escape($serverPathName), $driveLetter)
}
$fileLocation = Resolve-Path $fileLocation
Write-Output $fileLocation
$newText = read-host "Please enter text to append"
$newText = $newText -replace '\s', '-'
$addMarket = read-host "Are folders split by market? [Y/N]"
if ($addMarket -eq "N") {
Get-ChildItem -Path $fileLocation -Recurse -Filter "*.zip" -File |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ((($_.BaseName -replace '\s', '-') + "-" + $newText + $_.Extension))
}
}
elseif ($addMarket -eq "Y") {
Get-ChildItem -Path $fileLocation -Recurse -Filter "*.zip" -File |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ((($_.BaseName -replace '\s', '-') + "-" + (($_.Directory.Name -replace '\s', '-')) + "-" + $newText + $_.Extension).ToLower())
}
}
else {
write-host "ERROR! Incorrect Input!"
Write-Host "Exiting Script..."
Exit
}
Clear-Host
My goal is to create a PS script that does the following:
a) scans a source directory and produces a list of files that have a matching pattern that I pass in found inside the files
b) take the list of those files and Copy-Item to move and then archive it.
I have this process working for when I "-Filter" on a filename, but can't seem to get my script to work when using "Select-String -pattern". When it gets to the "$FileNames = #($Files | %{$_.Path.Substring($Source.Length)})" part of the code, it says file does not exist as its passing in the #{Path} code?
View of error
If ({$PatternIdentifier -ne "" -and $FileIdentifier -eq "" -and $FileExtension -ne ""})
{
$Files = get-childitem $Source -Filter $FileExtension | Select-String -pattern $PatternIdentifier -SimpleMatch |Select Path
}
$FileNames = #($Files | %{$_.Path.Substring($Source.Length)})
if($Files.Count -ne 0)
{
if ((test-path $ArchiveDestination) -eq 0)
{
New-Item -ItemType Directory -Force -Path $ArchiveDestination
}
foreach ($File in $Files)
{
Copy-Item $File -Destination $DestinationFolder
Copy-Item $File -Destination $ArchiveDestination
$count++
}
if($error.length -lt 0)
{
Write-Host ("Copied {0} files!!" -f $count)
$answer = $FilesTotalCount -$count
}
}
Get the value from the property:
|Select -expand path
Here is the code I am currently using:
# Don't include "\" at the end of $NewSource - it will stop the script from
# matching first-level subfolders
$ignore = "somename"
$files = gci $NewSource -recurse | Where {
$_.Extension -match "zip||prd" -and $_.FullName -notlike $ignore
}
foreach ($file in $files) {
$NewSource = $file.FullName
# Join-Path is a standard Powershell cmdLet
$destination = Join-Path (Split-Path -Parent $file.FullName) $file.BaseName
Write-Host -Fore green $destination
$destination = "-o" + $destination
# Start-Process needs the path to the exe and then the arguments passed
# separately. You can also add -wait to have the process complete before
# moving to the next
Start-Process -FilePath "C:\Program Files\7-Zip\7z.exe" -ArgumentList "x -y $NewSource $destination" -Wait
}
However, once it is finished I need to go back through the new directories and unzip my .prd files that are created only after unzipping the .zip archives. Need some help here as my tries aren't working and currently unzip and overwrite all the previously unzipped .prd and .zip files.
I already told you that $_.Extension -match "zip||prd" matches all extensions, because of the empty string between the two | characters in the regular expression (all strings contain the empty string).
Also, the -notlike and -like operators behave exactly like the -ne and -eq operators when comparing a value with a pattern that doesn't have wildcards in it, so your second condition will match all files whose full name isn't exactly "somename".
Change this:
$ignore = "somename"
$files = gci $NewSource -recurse | Where {
$_.Extension -match "zip||prd" -and $_.FullName -notlike $ignore
}
into this:
$ignore = "*somename*"
$files = gci $NewSource -recurse | Where {
$_.Extension -match "zip|prd" -and $_.FullName -notlike $ignore
}
and the code should do what you expect.
As an alternative you could build a list of the paths you want to ignore
$ignore = 'C:\path\to\first.zip',
'C:\other\path\to\second.zip',
'C:\some\file.prd',
...
and use the -notin (PowerShell v3 or newer) or -notcontains operator to exclude those files:
$_.FullName -notin $ignore
$ignore -notcontains $_.FullName
As a side note, I'd use the call operator and splatting instead of Start-Process for invoking 7zip.exe:
$destination = Join-Path (Split-Path -Parent $file.FullName) $file.BaseName
$params = 'x', '-y', $NewSource, "-o$destination"
& "${env:ProgramFiles}\7-Zip\7z.exe" #params
To also extract .prd files that were extracted from the zip archives add another step to your loop.
foreach ($file in $files) {
...
& "${env:ProgramFiles}\7-Zip\7z.exe" #params
Get-ChildItem $destination | Where-Object {
$_.Extension -eq 'prd'
} | ForEach-Object {
# extract matching file here, procedure is the
# same as with the files in the outer loop
}
}
You may want to wrap the code for building the destination path and extracting the file in a function that reads paths from the pipeline and calls itself recursively if the destination path contains .prd files.
function Invoke-Unzip {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[ValidateScript({Test-Path -LiteralPath $_})]
[string]$FullName
)
$newSource = $FullName
...
& "${env:ProgramFiles}\7-Zip\7z.exe" #params
Get-ChildItem $destination |
Where-Object { $_.Extension -eq 'prd' } |
Invoke-Unzip
}
I'm writing a script to delete pdf files older than 6 months in folder with the 'Email' prefix.
However, my second dir command within my foreach never runs, its code is blocked.
$Now = Get-Date;
$DaysTillDelete = "180";
$LastWrite = $Now.AddDays(-$DaysTillDelete);
$TargetFolder = "C:\Test EMDATA\EMDATA\";
$BackupPath = "\\SHPFS02\IT\EmPower Old";
$EmailFolders = #();
if(-Not(Test-Path -path ($TargetFolder + "\OldFiles" ))) {
mkdir -p ($TargetFolder +"\OldFiles");
}
$Network = Test-Path $BackupPath
#New-PSDrive -Name O -PSProvider FileSystem -Root "$BackupPath"; #-Credential $cred
Write-Host "Running Script"
dir $TargetFolder | %{
# Only delete files with the Email prefix
$name = $_.Name;
if ($_.Name.Length -le 5) {return;}
$id = $_.Name.SubString(0,5);
if ($id -eq "Email")
{
Write-Host "Found slip folder"
$EmailFolders += $TargetFolder + $_;
}
}
ForEach ($folder in $EmailFolders)
{
Write-Host $folder;
dir -path $folder -include *.pdf | %{
Write-Host "Checking" $name;
# Only select files older than 6 months
if( $_.LastWriteTime -le "$LastWrite")
{
$activeItem = Get-Item $TargetFolder + $_;
#Move files into oldfiles
Write-Host $TargetFolder
move-item -path $activeItem -destination ($TargetFolder + "OldFiles\");
if ($Network)
{
move-item -path $activeItem -destination "O:\";
}
Write-Host $_;
remove-item $activeItem;
Write-Host "Deleting" + $name;
}
}
}
The script works till line 31 but doesn't continue on past line 32 and being a fairly beginner PS user I can't see why.
Only use -include with the -recurse parameter.
http://technet.microsoft.com/en-us/library/hh849800.aspx
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to the contents of a directory,
such as C:\Windows*, where the wildcard character specifies the
contents of the C:\Windows directory.
What you want instead is the -filter parameter:
dir -path $folder -filter *.pdf