How I can do this powershell script better? - powershell

When I move FenrirFS profile to another, paths in directories become wrong.
So I decided to make a ps script to resolve it.
$wdir = "files" # constant part of path
$path = $PSScriptRoot # path to script
$pfix = "Target=" # prefix of path
$files = Get-ChildItem -Path $path -Filter *.alias | Where { ! $_.PSIsContainer } | Select -Expand Name
foreach ($file in $files)
{
$filec = Get-Content $file
$nlin = 0 # counter of line
foreach ($line in $filec)
{
if($line.Contains($pfix))
{
$nline = $pfix + $path + '\' + $wdir + ($line -split $wdir)[1]
$filec[$nlin] = $filec[$nlin].replace($line,$nline)
$filec | Set-Content $file
break
}
$nlin++
}
}
It's work, but I have a lot of files, which I should replace.
And $filec | Set-Content $file a little bit dumby, cuz I need to replace only one line.
Example of file:
Target=E:\home\prj\polygon\ps\files\NoDigital\godTech_2.JPG
DisplayName=godTech_2.JPG
WorkDir=
Arguments=
ShowCmd=0
ps script is located in the directory with aliases.
p.s. powershell 5.1

You could use the much faster switch for that:
$wdir = "files" # constant part of path
$path = $PSScriptRoot # path to script
$pfix = "Target=" # prefix of path
$files = Get-ChildItem -Path $path -Filter '*.alias' -File | ForEach-Object {
$content = switch -Regex -File $_.FullName {
"^$pfix" {
$oldPath = ($_ -split '=', 2)[-1].Trim()
$childPath = Join-Path -Path $wdir -ChildPath ($oldPath -split $wdir, 2)[-1]
# output the new path
"$pfix{0}" -f (Join-Path -Path $path -ChildPath $childPath)
}
default { $_ }
}
$content | Set-Content -Path $_.FullName -Force
}

Related

Find similarly-named files, and if present, remove the files without a specific string using PowerShell

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

PowerShell Script (foreach-loop problems)

I have a short question, but I am standing on the wall for too long now, so I have to ask you....
The situation is:
I have a special filetype, in different folders and subfolders.
I already managed to find the files, write them into a TXT-File and I also managed to split the path so I can name a ZIP-File with the Folder-Name and Date.
But the only thing I do not get is how to only zip the special file of folder1 in a Zip-archiv "folder1-date.zip" and the file of folder2 in a Zip-archiv "folder2-date.zip".
Code part looks like this:
[string[]]$dirs = (Split-Path (Split-Path -Path $output -Parent) -Leaf | Foreach-Object { $i++; $_ })
[string[]]$arrayFromFile = Get-content -Path 'C:\TEMP\output.txt'
foreach ($file in $arrayFromFile) {
foreach ($dir in $dirs){
#
Compress-Archive -Path $file -CompressionLevel Optimal -Update -DestinationPath $destination\$dir-$date.zip }
}
The Problem is, that every file with the extension found is in every ZIP-Archiv (logic because it is a foreach in a foreach) but I can not find the right way to do it....
Thank you for the help!
This will get the desired outcome and not have to save output to a text file.
$origin = "C:\TEMP\"
$filetyp = ".stl, .vol, .pct, .tif"
$destination = "C:\Daten\zipstore\"
$date = $(Get-Date -Format d)
$fileNames = Get-ChildItem "$origin" -Recurse | Where {$_.extension -eq ".stl"} | ForEach-Object { $_.FullName }
foreach ($file in $fileNames) {
$dir = (Split-Path (Split-Path -Path $file -Parent) -Leaf)
Compress-Archive -Path $file -CompressionLevel Optimal -Update -DestinationPath $destination\$dir-$date.zip
}
Thank you for the answer! Here you have the whole code:
# Variablen Definition:
#
$origin = "C:\TEMP\"
$filetyp = ".stl, .vol, .pct, .tif"
$destination = "C:\Daten\zipstore\"
$date = $(Get-Date -Format d)
#
#
# Auslesen aller Files die bestimmte Dateiendung haben:
#
Get-ChildItem "$origin" -Recurse | Where {$_.extension -eq ".stl"} | ForEach-Object { $_.FullName } > C:\TEMP\output.txt
#
#
# Remove filename, keep path to file / split and only keep last directory:
#
$output = Get-content C:\TEMP\output.txt
$i = 0
#
[string[]]$dirs = (Split-Path (Split-Path -Path $output -Parent) -Leaf | Foreach-Object { $i++; $_ })
#
#
# Create ZIP-Archiv:
#
[string[]]$arrayFromFile = Get-content -Path 'C:\TEMP\output.txt'
foreach ($file in $arrayFromFile) {
foreach ($dir in $dirs){
#
Compress-Archive -Path $file -CompressionLevel Optimal -Update -DestinationPath $destination\$dir-$date.zip }
}
#
#
# Delete files not needed anymore:
Remove-Item -Path $origin -Include *.txt -Recurse -Force
#
Maybe this helps!

How to parse through folders and files using PowerShell?

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 ' ')

How to use Where-Object and StartsWith to filter out files based on contents - Powershell v4.0

I'm trying to work through SQL files in a folder, renaming them and if the file contents starts with '/**' then I want to remove the first three lines from the file contents and overwrite the file.
The code below works but when adding one of the commented out lines it returns error messages.
function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
Split-Path $Invocation.MyCommand.Path
}
$sqlfolder = Get-ScriptDirectory
$files = Get-ChildItem $sqlfolder\* -Include *.sql
Foreach ($file in $files) {
Write-Output $file.name
$newName = $file.name.Substring($file.name.LastIndexOf(" ")+1)
Rename-Item $file -NewName $newName
get-content $file |
##Where-Object {$_.StartsWith("/**")} |
##Where-Object {$_ -Like "/**"} |
select -Skip 3 |
set-content "$file-temp"
move "$file-temp" $file -Force
}
I'm wanting one of the commented out lines to ensure that the first three lines will only be removed from files starting with the string '/**'.
Error message:
move : Cannot find path ...-temp' because it does not exist.
... ObjectNotFound ... PathNotFound ...
I was able to only modify files starting with the text '/**' by using an If as suggested in the comments. Here is the final code:
function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
Split-Path $Invocation.MyCommand.Path
}
$sqlfolder = Get-ScriptDirectory
$files = Get-ChildItem $sqlfolder\* -Include *.sql
Foreach ($file in $files) {
# Write-Output $file.name
$newName = $file.name.Substring($file.name.LastIndexOf(" ")+1)
Rename-Item $file -NewName $newName
$firstLine = (Get-Content $file)[0]
if ($firstLine.StartsWith("/**")){
Get-Content $file |
select -Skip 3 |
set-content "${file}-temp"
move "${file}-temp" $file -Force
}
}

Script working for 1 directory path but not for multiple directory paths

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.