Trouble with Log progression - powershell

I'm trying copy logs in numerical order and I want my output.txt to log the last file copied however I'm running to a problem where when my script goes from log_9.txt to Log_10.txt the value that gets put into my text file stays at log_9.txt even though it copies all the files
dir c:\PS1 *.bat | ForEach {
$variable = "$($_.Name) 'n$(Get-content $_.FullName)"
Set-Content -Value $variable -Path c:\PS1\Output.txt
$pull = Get-Content C:\PS1\Output.txt
copy-item $source\$pull -Destination $dest -Verbose
}
}

The following command shows you how you sort the base name (file name without extension) of your input files first lexically, by the text before the _, and then numerically, by the number following the _:
# The input simulates dir (Get-ChildItem) output.
#{ BaseName = 'log_10' }, #{ BaseName ='log_9' }, #{ BaseName = 'log_2' } |
Sort-Object { ($_.BaseName -split '_')[0] }, { [int] ($_.BaseName -split '_')[-1] }
The above yields the following - note the correct numerical sorting:
Name Value
---- -----
BaseName log_2
BaseName log_9
BaseName log_10

Related

How to add MD5 hash toa PowerShell file dump

Looking for a line to add that pulls the file information as below but includes an MD5 hash
It can be from certutil, but there is not a means to download that module so looking for a means that uses PowerShell without an additional update of PowerShell.
We are looking to compare two disks for missing files even when the file might be located in an alternate location.
cls
$filPath="G:/"
Set-Location -path $filPath
Get-ChildItem -Path $filPath -recurse |`
foreach-object{
$Item=$_
$Path =$_.FullName
$ParentS=($_.FullName).split("/")
$Parent=$ParentS[#($ParentS.Length-2)]
$Folder=$_.PSIsContainer
#$Age=$_.CreationTime
#$Age=$_.ModifiedDate
$Modified=$_.LastWriteTime
$Type=$_.Extension
$Path | Select-Object `
#{n="Name";e={$Item}},`
#{n="LastModified";e={$Modified}},`
#{n="Extension";e={$Type}},`
#{n="FolderName";e={if($Parent){$Parent}else{$Parent}}},`
#{n="filePath";e={$Path}}`
} | Export-csv Q:/lpdi/fileDump.csv -NoTypeInformation
Possible answer here: (Thanks Guenther)
#{name="Hash";expression={(Get-FileHash -Algorithm MD5 -Path $Path).hash}}
In this script it meets the filehash condition along with the name of the file which allows a way to find the file on the folder and know it matches another one in another location based on the hash.
I'm not sure what happens on the file hash itself. If it includes the name of the file, the hash will be different. If it is only the file itself and the path doesn't matter, it should meet the requirement. I'm not sure how to include it in the code above however
Your code could be simplified so you don't need all those 'in-between' variables.
Also, the path separator character in Windows is a backslash (\), not a forward slash (/) which makes this part of your code $ParentS=($_.FullName).split("/") not doing what you expect from it.
Try
$SourcePath = 'G:\'
Get-ChildItem -Path $SourcePath -File -Recurse | ForEach-Object {
# remove the next line if you do not want console output
Write-Host "Processing file '$($_.FullName)'.."
$md5 = ($_ | Get-FileHash -Algorithm MD5).Hash
$_ | Select-Object #{Name = 'Name'; Expression = { $_.Name }},
#{Name = 'LastModified'; Expression = { $_.LastWriteTime }},
#{Name = 'Extension'; Expression = { $_.Extension }},
#{Name = 'FolderName'; Expression = { $_.Directory.Name }},
#{Name = 'FilePath'; Expression = { $_.FullName }},
#{Name = 'FileHash'; Expression = { $md5 }}
} | Export-Csv -Path 'Q:/lpdi/fileDump.csv' -NoTypeInformation
Because getting hash values is a time consuming process I've added a Write-Host line, so you know the script did not 'hang'..
Edit: Okay so, here is my workaround as promised.
Before we start, requirements are:
Have python 3.8 or above installed and registered in windows PATH
edit the ps1 file variables accordingly
edit the python file variables accordingly
bypass powershell script execution policies
There are 4 files in the working directory (different from your target directory):
addMD5.ps1 (static)
addMD5.py (static)
fileDump-original.csv (auto-generated)
fileDump-modified.csv (auto-generated)
Here are the contents of those 4 files:
addMD5.ps1
$targetDir="C:\Users\USERname4\Desktop\myGdrive"
$workingDir="C:\Users\USERname4\Desktop\myWorkingDir"
$pythonName="addMD5.py"
$exportName = "fileDump-original.csv"
Set-Location -path $workingDir
if (Test-Path $exportName)
{
Remove-Item $exportName
}
Get-ChildItem -Path $targetDir -recurse |`
foreach-object{
$Item=$_
$Path =$_.FullName
$ParentS=($_.FullName).split("/")
$Parent=$ParentS[#($ParentS.Length-2)]
$Folder=$_.PSIsContainer
#$Age=$_.CreationTime
#$Age=$_.ModifiedDate
$Modified=$_.LastWriteTime
$Type=$_.Extension
$Path | Select-Object `
#{n="Name";e={$Item}},`
#{n="LastModified";e={$Modified}},`
#{n="Extension";e={$Type}},`
#{n="FolderName";e={if($Parent){$Parent}else{$Parent}}},`
#{n="filePath";e={$Path}}`
} | Export-csv $exportName -NoTypeInformation
python $pythonName
addMD5.py
import os, hashlib
def file_len(fname):
with open(fname) as fp:
for i, line in enumerate(fp):
pass
return i + 1
def read_nth(fname,intNth):
with open(fname) as fp:
for i, line in enumerate(fp):
if i == (intNth-1):
return line
def getMd5(fname):
file_hash = hashlib.md5()
with open(fname, "rb") as f:
chunk = f.read(8192)
while chunk:
file_hash.update(chunk)
chunk = f.read(8192)
return file_hash.hexdigest()
file1name = "fileDump-original.csv"
file2name = "fileDump-modified.csv"
try:
os.remove(file2name)
except:
pass
file2 = open(file2name , "w")
for linenum in range(file_len(file1name)):
if (linenum+1) == 1:
file2.write(read_nth(file1name,linenum+1).strip()+',"md5"\n')
else:
innerfilename = read_nth(file1name,linenum+1).split(",")[4].strip()[1:-1]
file2.write(read_nth(file1name,linenum+1).strip()+',"'+getMd5(innerfilename)+'"\n')
file2.close()
fileDump-original.csv
"Name","LastModified","Extension","FolderName","filePath"
"test1.txt","20-Jun-21 12:50:44 PM",".txt","C:\Users\USERname4\Desktop\myGdrive\test1.txt","C:\Users\USERname4\Desktop\myGdrive\test1.txt"
"test2.txt","20-Jun-21 12:50:37 PM",".txt","C:\Users\USERname4\Desktop\myGdrive\test2.txt","C:\Users\USERname4\Desktop\myGdrive\test2.txt"
fileDump-modified.csv
"Name","LastModified","Extension","FolderName","filePath","md5"
"test1.txt","20-Jun-21 12:50:44 PM",".txt","C:\Users\USERname4\Desktop\myGdrive\test1.txt","C:\Users\USERname4\Desktop\myGdrive\test1.txt","d659c1bc0a3010b0bdd45d9a8fee3196"
"test2.txt","20-Jun-21 12:50:37 PM",".txt","C:\Users\USERname4\Desktop\myGdrive\test2.txt","C:\Users\USERname4\Desktop\myGdrive\test2.txt","d55749658669d28f8549d94cd01b72ba"

Export CSV. Folder, subfolder and file into separate column

I created a script that lists all the folders, subfolders and files and export them to csv:
$path = "C:\tools"
Get-ChildItem $path -Recurse |select fullname | export-csv -Path "C:\temp\output.csv" -NoTypeInformation
But I would like that each folder, subfolder and file in pfad is written into separate column in csv.
Something like this:
c:\tools\test\1.jpg
Column1
Column2
Column3
tools
test
1.jpg
I will be grateful for any help.
Thank you.
You can split the Fullname property using the Split() method. The tricky part is that you need to know the maximum path depth in advance, as the CSV format requires that all rows have the same number of columns (even if some columns are empty).
# Process directory $path recursively
$allItems = Get-ChildItem $path -Recurse | ForEach-Object {
# Split on directory separator (typically '\' for Windows and '/' for Unix-like OS)
$FullNameSplit = $_.FullName.Split( [IO.Path]::DirectorySeparatorChar )
# Create an object that contains the splitted path and the path depth.
# This is implicit output that PowerShell captures and adds to $allItems.
[PSCustomObject] #{
FullNameSplit = $FullNameSplit
PathDepth = $FullNameSplit.Count
}
}
# Determine highest column index from maximum depth of all paths.
# Minus one, because we'll skip root path component.
$maxColumnIndex = ( $allItems | Measure-Object -Maximum PathDepth ).Maximum - 1
$allRows = foreach( $item in $allItems ) {
# Create an ordered hashtable
$row = [ordered]#{}
# Add all path components to hashtable. Make sure all rows have same number of columns.
foreach( $i in 1..$maxColumnIndex ) {
$row[ "Column$i" ] = if( $i -lt $item.FullNameSplit.Count ) { $item.FullNameSplit[ $i ] } else { $null }
}
# Convert hashtable to object suitable for output to CSV.
# This is implicit output that PowerShell captures and adds to $allRows.
[PSCustomObject] $row
}
# Finally output to CSV file
$allRows | Export-Csv -Path "C:\temp\output.csv" -NoTypeInformation
Notes:
The syntax Select-Object #{ Name= ..., Expression = ... } creates a calculated property.
$allRows = foreach captures and assigns all output of the foreach loop to variable $allRows, which will be an array if the loop outputs more than one object. This works with most other control statements as well, e. g. if and switch.
Within the loop I could have created a [PSCustomObject] directly (and used Add-Member to add properties to it) instead of first creating a hashtable and then converting to [PSCustomObject]. The choosen way should be faster as no additional overhead for calling cmdlets is required.
While a file with rows containing a variable number of items is not actually a CSV file, you can roll your own and Microsoft Excel can read it.
=== Get-DirCsv.ps1
Get-Childitem -File |
ForEach-Object {
$NameParts = $_.FullName -split '\\'
$QuotedParts = [System.Collections.ArrayList]::new()
foreach ($NamePart in $NameParts) {
$QuotedParts.Add('"' + $NamePart + '"') | Out-Null
}
Write-Output $($QuotedParts -join ',')
}
Use this to capture the output to a file with:
.\Get-DirCsv.ps1 | Out-File -FilePath '.\dir.csv' -Encoding ascii

Search and replace files and folders names with txt file support

I have many folders and inside these different files. Each folder and their children files have the same name and different extension, so in the ABC folder there are the ABC.png, ABC.prj, ABC.pgw files, in the DEF folder there are the DEF.png, DEF.prj, DEF.pgw files and so on.
With a script I have created a txt file with the list of png file names. Then I put in row 2 a new name for the name in row1, in row 4 a new name for the name in row 3, and so on.
Now I'm searching a powershell script that:
- scan all folder for the name in row 1 and replace it with name in row2
- scan all folder for the name in row 3 and replace it with name in row4 and so on
I have try with this below, but it doesn't work.
Have you some suggestions? Thank you
$0=0
$1=1
do {
$find=Get-Content C:\1\Srv\MapsName.txt | Select -Index $0
$repl=Get-Content C:\1\Srv\MapsName.txt | Select -Index $1
Get-ChildItem C:\1\newmaps -Recurse | Rename-Item -NewName { $_.name -replace $find, $repl} -verbose
$0=$0+2
$1=$1+2
}
until ($0 -eq "")
I believe there are several things wrong with your code and also the code Manuel gave you.
Although you have a list of old filenames and new filenames, you are not using that in the Get-ChildItem cmdlet, but instead try and replace all files it finds.
Using -replace uses a Regular Expression replace, that means the special character . inside the filename is regarded as Any Character, not simply a dot.
You are trying to find *.png files, but you do not add a -Filter with the Get-ChildItem cmdlet, so now it will return all filetypes.
Anyway, I have a different approach for you:
If your input file C:\1\Srv\MapsName.txt looks anything like this:
picture1.png
ABC_1.png
picture2.png
DEF_1.png
picture3.png
DEF_2.png
The following code will use that to build a lookup Hashtable so it can act on the files mentioned in the input file and leave all others unchanged.
$mapsFile = 'C:\1\Srv\2_MapsName.txt'
$searchPath = 'C:\1\NewMaps'
# Read the input file as an array of strings.
# Every even index contains the file name to search for.
# Every odd index number has the new name for that file.
$lines = Get-Content $mapsFile
# Create a hashtable to store the filename to find
# as Key, and the replacement name as Value
$lookup = #{}
for ($index = 0; $index -lt $lines.Count -1; $index += 2) {
$lookup[$lines[$index]] = $lines[$index + 1]
}
# Next, get a collection of FileInfo objects of *.png files
# If you need to get multiple extensions, remove the -Filter and add -Include '*.png','*.jpg' etc.
$files = Get-ChildItem -Path $searchPath -Filter '*.png' -File -Recurse
foreach ($file in $files) {
# If the file name can be found as Key in the $lookup Hashtable
$find = $file.Name
if ($lookup.ContainsKey($find)) {
# Rename the file with the replacement name in the Value of the lookup table
Write-Host "Renaming '$($file.FullName)' --> $($lookup[$find])"
$file | Rename-Item -NewName $lookup[$find]
}
}
Edit
If the input text file 'C:\1\Srv\MapsName.txt' does NOT contain filenames including their extension, change the final foreach loop into this:
foreach ($file in $files) {
# If the file name can be found as Key in the $lookup Hashtable
# Look for the file name without extension as it is not given in the 'MapsName.txt' file.
$find = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
if ($lookup.ContainsKey($find)) {
# Rename the file with the replacement name in the Value of the lookup table
# Make sure to add the file's extension if any.
$newName = $lookup[$find] + $file.Extension
Write-Host "Renaming '$($file.FullName)' --> '$newName'"
$file | Rename-Item -NewName $newName
}
}
Hope that helps
The problem in your snippet is that it never ends.
I tried it and it works but keeps looping forever.
I created a folder with the files a.txt, b.txt and c.txt.
And in the map.txt I have this content:
a.txt
a2.md
b.txt
b2.md
c.txt
c2.md
Running the following script I managed to rename every file to be as expected.
$0=0
$1=1
$find=Get-Content D:\map.txt | Select -Index $0
while($find) {
$find=Get-Content D:\map.txt | Select -Index $0
$repl=Get-Content D:\map.txt | Select -Index $1
if(!$find -Or !$repl) {
break;
}
Get-ChildItem D:\Files -Recurse | Rename-Item -NewName { $_.name -replace $find, $repl} -verbose
$0=$0+2
$1=$1+2
}

Append file name using CSV

I'm trying to rename files that match values in column one of a csv adding the value in column 3 to the beginning of the file name leaving the rest of the file name intact. Here is what I have so far. I cant seem to figure out the Rename-Item.
# Common Paths
$PathRoot = "C:\Temp\somefiles" #Where the files are to rename
# Get csv file
$ClientAccounts = Import-CSV -path "\\server\some\path\to\csv\file.csv"
# Get each file and rename it
ForEach($row in $ClientAccounts)
{
$CurrentClientTaxId = $row[-1].TaxId
$CurrentClientName = $row[-1].ClientName
#loop through files
$FileExists = Test-Path -Path "$PathTotal\*$CurrentClientLB_Number*" #See if there is a file.
If ($FileExists -eq $true) #The file does exist.
{
#ReName File
Rename-Item -Path $PathRoot -NewName {$CurrentClientName + " " + $_.name}
}
}
Lets suppose your CSV file looks similar to this:
"LB_Number","TaxId","ClientName"
"987654","12345","Microsoft"
"321456","91234","Apple"
"741852","81234","HP"
Column 1 has the portion of the existing file name to match
Column 3 has the client name you want to prepend to the file name
Then your function could be something like this:
# Common Paths
$PathRoot = "C:\Temp\somefiles" # Where the files are to rename
# Get csv file
$ClientAccounts = Import-CSV -path "\\server\some\path\to\csv\file.csv"
# Loop through all clients in the CSV
foreach($client in $ClientAccounts) {
$CurrentClientLB_Number = $client.LB_Number
$CurrentClientTaxId = $client.TaxId # unused...??
$CurrentClientName = $client.ClientName
# get the file(s) using wildcards (there can be more than one)
# and rename them
Get-ChildItem -Path "$PathRoot\*$CurrentClientLB_Number*" -File | ForEach-Object {
$_ | Rename-Item -NewName ($CurrentClientName + " " + $_.Name)
}
# Curly braces work also, although this is not very common practice:
# Get-ChildItem -Path "$PathRoot\*$CurrentClientLB_Number*" -File |
# Rename-Item -NewName { ($CurrentClientName + " " + $_.Name) }
}
I use the -File parameter with Get-ChildItem so the function will only return files; not directories. If you are using PowerShell version 2.0, you need to replace that with | Where-Object { !$_.PSIsContainer }.

PowerShell - Extracting the Metadata of of files and grid viewing it

Please see the following code:
# import .NET 4.5 compression utilities
Add-Type -As System.IO.Compression.FileSystem;
$zipArchives = Get-ChildItem "*.zip";
foreach($zipArchive in $zipArchives)
{
$archivePath = $zipArchive.FullName;
$archive = [System.IO.Compression.ZipFile]::OpenRead($archivePath);
try
{
foreach($archiveEntry in $archive.Entries)
{
if($archiveEntry.FullName -notmatch '/$')
{
$tempFile = [System.IO.Path]::GetTempFileName();
try
{
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($archiveEntry, $tempFile, $true);
$windowsStyleArchiveEntryName = $archiveEntry.FullName.Replace('/', '\');
Select-String -pattern "<dc:title>.*</dc:title>" -path (Get-ChildItem $tempFile) | Select-Object #{Name="Path";Expression={Join-Path $archivePath (Split-Path $windowsStyleArchiveEntryName -Parent)}}
#Select-String -pattern "<dc:title>.*</dc:title>" -path (Get-ChildItem $tempFile) | Select-Object Matches
#Select-String -pattern "<dc:subject>.*</dc:subject>" -path (Get-ChildItem $tempFile) | Select-Object Matches
#Select-String -pattern "<dc:date>.*</dc:date>" -path (Get-ChildItem $tempFile) | Select-Object Matches
}
finally
{
Remove-Item $tempFile;
}
}
}
}
finally
{
$archive.Dispose();
}
}
It's a modified version of code that I found on the internet and helped me to find strings inside zip files.
My intention now is to extract metadata from zip files using this code.
I don't understand how can I display the two types of information in separate lines. If you run the script with only one Select-String... pipeline line active, the code works as expected. If you activate (uncomment) the second Select-String... pipeline line, the second type of information (the <dc:title> value) is not displayed and instead there is a blank line.
Please help me:
1) How can I also display the dc:title value using the Select-String | Select-Object mechanism that I used in the code.
2) How can I output all the data in a table format, so the table would look something like this:
* ZIP Filename * DC Title *
* zipfile01.zip * Bla Bla 01 *
* zipfile02.zip * Bla Bla 02 *
* zipfile03.zip * Bla Bla 03 *
This format of output would be the most usable for me.
The console "view" for pipeline-objcts is created based on the first object (which only have a Path-property). The second object is missing a Path-property which is why you see a blank line. If you had commented out the first Select-String ..-line (that shows Path), then the second line would work.
Objects sent through the pipeline should have the same set of properties so avoid using select-object with different property-sets. Ex:
.....
$tempFile = [System.IO.Path]::GetTempFileName();
try
{
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($archiveEntry, $tempFile, $true);
[System.IO.Compression.ZipFileExtensions]::
$windowsStyleArchiveEntryName = $archiveEntry.FullName.Replace('/', '\');
Select-String -pattern "<dc:title>(.*)</dc:title>" -path (Get-ChildItem $tempFile) | Select-Object #{n="Zip FileName";e={$zipArchive.Name}}, #{Name="DC Title";Expression={ $_.Matches.Groups[1].Value}}
}
finally
{
Remove-Item $tempFile;
}
.....
To output all the metadata, you should create an object that includes all the values. Ex:
$tempFile = [System.IO.Path]::GetTempFileName();
try
{
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($archiveEntry, $tempFile, $true);
[System.IO.Compression.ZipFileExtensions]::
$windowsStyleArchiveEntryName = $archiveEntry.FullName.Replace('/', '\');
#Avoid multiple reads
$content = Get-Content $tempFile
New-Object -TypeName psobject -Property #{
"Zip Filename" = $zipArchive.Name
"DC Title" = if($content -match '<dc:title>(.*)</dc:title>') { $Matches[1] } else { $null }
"DC Subject" = if($content -match '<dc:subject>(.*)</dc:subject>') { $Matches[1] } else { $null }
"DC Date" = if($content -match '<dc:date>(.*)</dc:date>') { $Matches[1] } else { $null }
}
}
finally
{
Remove-Item $tempFile;
}
....
Ex. output
Zip Filename DC Subject DC Title DC Date
------------ ---------- -------- -------
test.zip Subject O M G 5/18/2016
If you really want to force separate views (will get ugly), then you need to send he objects to | Out-Default to create a new view every time, ex:
Select-String -pattern "<dc:title>.*</dc:title>" -path (Get-ChildItem $tempFile) | Select-Object #{Name="Path";Expression={Join-Path $archivePath (Split-Path $windowsStyleArchiveEntryName -Parent)}} | Out-Default
i know it's not the answer you were looking for, but as a temporary workaround, you may be able to combine the two commands into one like this
Select-String -pattern "<dc:title>.*</dc:title>" -path (Get-ChildItem $tempFile) | Select-Object Matches, #{Name="Path";Expression={Join-Path $archivePath (Split-Path $windowsStyleArchiveEntryName -Parent)}}