This question already has answers here:
Determine and Grab a Number Within Directory Name and Use That Number In an Expression
(2 answers)
Closed 1 year ago.
I recently wanted to improve my code by cleaning it up a bit. In my simple copy and paste program, I want to create a log file every time after the program ran. The problem is with naming the text file: I don’t want to overwrite any log-files and therefore give the file a unique name. Right now it looks like this:
start-transcript - Path $Sourcefolder\log-$logname_$logH.$logM.$logS
Basically it creates a log file based on the time you ran the program and a unique name will be given (I had to use hours, minutes and seconds in a seperate function otherwise special characters would be used but that’s not the problem)
The result would look like this:
log-08.11.202114.47.56
Not really that great.
My solution to the problem would be adding the variable $i and adding +1 every time the program run.
But if I try to do so, the value will automatically reset to 0 after it’s done.
My question, is there a way to implement this or is there another way?
(Sorry if bad format, currently on mobile)
Why not put an ISO8601-like timestamp in the log filename. Is granularity greater than one (1) second needed?
PS C:\> $LogFilename = 'log-' + (Get-Date -Format 'yyyyMMddTHHmmss') + '.log'
PS C:\> $LogFilename
log-20211108T083105.log
You could also use below reusable helper function to always ensure a unique filename:
function Get-UniqueFileName {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)]
[Alias('FullName')]
[string]$Path # the suggested full path and filename
)
$directory = [System.IO.Path]::GetDirectoryName($Path)
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($Path)
$extension = [System.IO.Path]::GetExtension($Path) # this includes the dot
# get an array of all files with the same extension currently in the directory
$allFiles = #(Get-ChildItem $directory -File -Filter "$baseName*$extension" | Select-Object -ExpandProperty Name)
# construct the possible new file name (just the name, not the full path and name)
$newFile = $baseName + $extension
$seed = 1
while ($allFiles -contains $newFile) {
# add the seed value between brackets. (you can ofcourse leave them out if you like)
$newFile = "{0}({1}){2}" -f $baseName, $seed, $extension
$seed++
}
# return the full path and filename
return Join-Path -Path $directory -ChildPath $newFile
}
In your case you can then do:
$Sourcefolder = 'D:\Test'
$logFile = Join-Path -Path $Sourcefolder -ChildPath ('{0:yyyyMMdd}.log' -f (Get-Date)) | Get-UniqueFileName
Example:
If a file called D:\Test\20211108.log already exists in your source folder, this will return a new filename D:\Test\20211108(1).log. The next one will be D:\Test\20211108(2).log etc.
or, as already commented, you can use the DateTime Ticks property te be pretty sure the filename will be unique:
$logFile = Join-Path -Path $Sourcefolder -ChildPath ('{0}.log' -f (Get-Date).Ticks)
Returns D:\Test\637719832681865978.log
Related
I hope you are all safe in this time of COVID-19.
I'm trying to generate a script that goes to the directory and compresses each file to .zip with the same name as the file, for example:
sample.txt -> sample.zip
sample2.txt -> sample2.zip
but I'm having difficulties, I'm not that used to powershell, I'm learning and improving this script. In the end it will be a script that deletes files older than X days, compresses files and makes them upload in ftp .. the part of excluding with more than X I've already managed it for days, now I grabbed a little bit on this one.
Last try at moment.
param
(
#Future accept input
[string] $InputFolder,
[string] $OutputFolder
)
#test folder
$InputFolder= "C:\Temp\teste"
$OutputFolder="C:\Temp\teste"
$Name2 = Get-ChildItem $InputFolder -Filter '*.csv'| select Name
Set-Variable SET_SIZE -option Constant -value 1
$i = 0
$zipSet = 0
Get-ChildItem $InputFolder | ForEach-Object {
$zipSetName = ($Name2[1]) + ".zip "
Compress-Archive -Path $_.FullName -DestinationPath "$OutputFolder\$zipSetName"
$i++;
$Name2++
if ($i -eq $SET_SIZE) {
$i = 0;
$zipSet++;
}
}
You can simplify things a bit, and it looks like most of the issues are because in your script example $Name2 will contain a different set of items than the Get-ChildItem $InputFolder will return in the loop (i.e. may have other objects other than .csv files).
The best way to deal with things is to use variables with the full file object (i.e. you don't need to use |select name). So I get all the CSV file objects right away and store in the variable $CsvFiles.
We can additionally use the special variable $_ inside the ForEach-Object which represents the current object. We also can use $_.BaseName to give us the name without the extension (assuming that's what you want, otherwise use $_Name to get a zip with the name like xyz.csv).
So a simplified version of the code can be:
$InputFolder= "C:\Temp\teste"
$OutputFolder="C:\Temp\teste"
#Get files to process
$CsvFiles = Get-ChildItem $InputFolder -Filter '*.csv'
#loop through all files to zip
$CsvFiles | ForEach-Object {
$zipSetName = $_.BaseName + ".zip"
Compress-Archive -Path $_.FullName -DestinationPath "$OutputFolder\$zipSetName"
}
I use a simple function to download files and return the path to me when updating computers for simplicity.
I was stuck on why it was not working then realized that the proxy is appending a random number to the filename so instead of it being 12345.zip it is actually 8493830_12345.zip.
I have tried to find the file using the "_" as a split but while there are no errors, the file is not being returned and I have checked it is there manually.
function FileCheck {
$fileName.Split("_")[1]
$fileName = "{0}.zip" -f 12345
Download -ZipFileName $($fileName) -OutputDirectory $env:temp
$SleepTime = 300
$sleepElapsed = 0
$sleepInterval = 20
Start-Sleep $sleepInterval
$file = Get-ChildItem -Path $env:temp -Filter "$fileName*"
if ($file -ne $null) {
return $file[0].FullName
}
Start-Sleep($sleepInterval)
$sleepElapsed += $sleepInterval
if (($SleepTime) -le $sleepElapsed){
# Check for file with given prefix
$file = Get-ChildItem -Path $env:temp -Filter "$fileName*"
if ($file -eq $null) {
Write-Error 'file not found'
return $null
}
return $file[0].FullName
}
}
I am guessing the split is not working but googling and moving the filename.split has not worked for me. Any help is appreciated
Well, your split is doing nothing at all. You haven't defined $filename, but if you had, and it had an underscore, then $filename.split('_') would return two or more strings, depending on how many underscores were in the original string, but you never capture the result. I think the real problem here is the filter you are applying to Get-ChildItem later in your function.
$file = Get-ChildItem -Path $env:temp -Filter "$fileName*"
That will look for files beginning with $fileName, which you define on line 4 to be "12345.zip". That is exactly the opposite of what you want to be looking for. You need to move the asterisk to before $fileName, so it looks like this:
$file = Get-ChildItem -Path $env:temp -Filter "*$fileName"
That will return all files that end with "12345.zip", which would include things like:
myfuzzyhippo12345.zip
learn-to-count-12345.zip
8493830_12345.zip
Basically anything that ends in 12345.zip. Also, it appears that you are under the impression that executing a return $file[0].fullname or return $null will stop the function. That's a mistake. A function runs to completion unless exited early by something like a break command. Also, everything not explicitly captured or redirected will be passed back from the function, so reading through your function people are likely to get the output of your $filename.split('_') line, then possibly $null or $filename[0].fullname.
Lastly, it appears that you're trying to look for the file, if you don't find it to wait a bit, and try again, until $sleepElapsed is greater than $sleepTime. What you want here is a While or a Do/While loop. Here's what I'd do...
function FileCheck {
Param(
$fileName = '12345.zip',
$SleepTime = 300,
$sleepElapsed = 0,
$sleepInterval = 20
)
Download -ZipFileName $($fileName) -OutputDirectory $env:temp
Do{
Start-Sleep $sleepInterval
$sleepElapsed = $sleepElapsed + $sleepInterval
$file = Get-ChildItem -Path $env:temp -Filter "*$fileName"|Select -First 1
}While(!$file -and $sleepElapsed -le $sleepTime)
$file.FullName
}
That lets you define things like sleep settings at runtime if you want, or just let it default to what you were using, same with the file name. Then it downloads the file, and looks for it, pausing between attempts, until either it finds the file, or it runs out of time. Then it returns $file.FullName which is either the path to the file if it found one, or nothing if it didn't find a file.
Personally I'd have it return the file object, and just utilize the .FullName property if that's all I wanted later. Usually (not always, but usually) more info returned from a function is better than less info. Like what if the download fails and it's a zero byte file? Just returning only the path doesn't tell you that.
I am running a powershell script, with the following
Copy-Item -Path c:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config -Destination c:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.orig
If the machine.orig already exists, how can i make it copy it to machine.orig1, and if that already exists, machine.orgin2?
If I may make a suggestion. Personally, I like the idea of a date/time stamp on the file rather than an incremental number. This way you know when the file was backed up, and you're far less likely to run into confusion over the file. Plus the script code is simpler.
Hope this helps.
$TimeStamp = get-date -f "MMddyyyyHHmmss"
$SourceFile = Dir c:\folder\file.txt
$DestinationFile = "{0}\{1}_{2}.{3}" -f $SourceFile.DirectoryName, $SourceFile.BaseName, $TimeStamp, $SourceFile.Extension
copy-Item $sourcefile $DestinationFile
#let us first define the destination path
$path = "R:\WorkindDirectory"
#name of the file
$file = "machine.orgin"
#let us list the number of the files which are similar to the $file that you are trying to create
$list = Get-ChildItem $path | select name | where name -match $file
#let us count the number of files which match the name $file
$a = ($list.name).count
#if such count of the files matching $file is less than 0 (which means such file dont exist)
if ($a -lt 1)
{
New-Item -Path $path -ItemType file -Name machine.orgin
}
#if such count of the files matching $file is greater than 0 (which means such/similar file exists)
if ($a -gt 0)
{
$file = $file+$a
New-Item -Path $path -ItemType file -Name $file
}
Note: This works assuming that the names of the files are in series. Let us say using this script you create
machine.orgin
machine.orgin1
machine.orgin2
machine.orgin3
and later delete machine.orgin2 and re run the same script then it won't work.
Here i have given an example where i have tried to create a new file and you can safely modify the same to copy such file using copy-item instead of new-item
I can see that the there have been similar question answered but nothing seems to have the answers I'm looking for, I have tried using the solutions/modifying them to suit my needs but am unable to get the correct outputs.
My main question is: Can you pass Powershell variables to 7zip's command line.
Currently i have the script:
##########Alias Setting##########
if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {throw "$env:ProgramFiles\7-Zip\7z.exe needed"}
set-alias sz "$env:ProgramFiles\7-Zip\7z.exe"
############################################
#### Variables
$filePath = "D:\Logs\test\201310\" #location to look in
$Archive = "D:\Logs\Test\201310\"
$Extension = "*.log" #extensions to look for
$Days = "30" #Number of days past today's date that will be archived
$CutDay = [DateTime]::Now.AddDays($Days)
$log = Get-ChildItem $filepath -include $Extension -recurse | Where-Object {$_.LastWriteTime -lt $CutDay}
########### END of VARABLES ##################
foreach ($File in $log) #current gui output - temp only using for debugging purpose if required and to confirm files.
{
write-host "File Name : $File " $File.LastWriteTime
}
foreach ($file in $log) # actually zipping
{
$archivename = $_.CutDay
sz a -t7z $archivename $archivename -m0=PPMd
}
########### END OF SCRIPT ##########
What i would like to have is have the 7zip command line read the file name and destination from variables, I plan to try and make this as a generic script as possible so the $archivename would depend on folder name and current date.
I would like the script to be able to read the locations it is zipping via parameters but currently I cannot get it to read the variables!
Any help would be appreciate or if you require clarification please let me know.
I figured out that the best way to pass parameter to an executable is using the splatting parameter (#variablename):
$7zipPath = "$env:ProgramFiles\7-Zip\7z.exe"
$parameter = #('a', '-t7z', '-m0=PPMd', "$target", "$source")
&$7zipPath #parameter
sz a -t7z $archivename $archivename -m0=PPMd
There are two $archivename variables you pass to 7zip. The first should be the Target (e.g. c:\test.zip) and the second the Source (e.g c:\test.text)
Pretty new to Powershell and hoping someone can point me in the right direction. Im trying to figure out if there is a cleaner way to accomplish what I have below? Is there a way to refresh to contents of Get-ChildItem once I have made some changes to the files which are returned during the first Get-ChildItem call (stored in $items variable)?
During the first foreach statement I am creating a log signature for all the files that are returned. Once that is done, what I need to do is; get a listing once again (because the item in the path have changed), the second Get-ChildItem will include both the files that were found during the first Get-ChildItem call and also all the logFiles that were generated when the first foreach statement called the generate-LogFile function. So my question, is there a way to update the listing without having to call get-chilItem twice, as well as use two foreach statements?
Thanks for all the help!
--------------This is what I changed the code based on recommendation--------------
$dataStorePath = "C:\Process"
function print-All($file)
{
Write-Host "PrintALL filename:" $file.FullName #Only prints when print-All($item) is called
}
function generate-LogFile($file)
{
$logName = $file.FullName + ".log"
$logFilehandle = New-Object System.IO.StreamWriter $logName
$logFilehandle.Writeline($logName)
$logFilehandle.Close()
return $logName
}
$items = Get-ChildItem -Path $dataStorePath
foreach ($item in $items)
{
$log = generate-LogFile($item) #Contains full path C:\Process\$fileName.log
print-All($item)
print-All($log) #When this goes to the function, nothing prints when using $file.FullName in the print-All function
}
---------Output--------------
For testing I have two files in C:\Process:
fileA.txt & fileB.txt
I will create two additional files
fileA.txt.log & fileB.txt.log
Eventually I need to do something with all four files. I created a print-All Function where I would process all four files. Below is the current ouput. As can be seen, I only get output for the two original files found, not the two additional created (get blank lines when calling the print-All($log)). I need to able to use fullpath property provided by Get-ChildItem, thus using FullName
PrintALL filename: fileA.txt
PrintALL filename:
PrintALL filename: fileB.txt
PrintALL filename:
I'm not entirely clear on what you are asking, by can have generate-LogFile return the created log file, then just call generateRequestData on both your file and the log file? Something like this:
$items = Get-ChildItem -Path $dataStorePath
foreach ($file in $items)
{
$logFile = generate-LogFile $file
generateRequestData $file
generateRequestData $logFile
}
Edit:
In your added sample, you are returning a string from generate-LogFile. .NET strings don't have a FullName property, so nothing gets printed in print-All. To get the FileInfo object that you want, use the get-item commandlet:
return Get-Item $logName
Also, in this example, you don't need to use a StreamWriter to write to the file, you could use the native powershell Out-File commandlet:
function generate-LogFile($file)
{
$logName = $file.FullName + ".log"
$logName | Out-File $logName
return Get-Item $logName
}