I have a PowerShell script that gets a list of file names from a file, searches a folder for the file names, archives them, and then does other stuff.
#make non-terminating errors behave like terminating errors (at script level)
$ErrorActionPreference = "Stop"
#set the folder that has the list and the files
$some_path = "D:\some_folder\"
$archive = "D:\archive\"
#set the list file name
$file_list = $some_path + "file_list.txt"
#get the files that I'm searching for from this list file
$files_to_retrieve = Select String -Path $file_list -Pattern "something" | Select-Object Line
#get the number of files for this search string
$n = $file_list.Length - 1
#seed the while loop counter
$i = 0
#while loop to archive and modify the files
While ($i -le $n)
{
#set the current file name
$current_file = $path + $files_to_retrieve[$i].Line
try
{
Copy-Item -Path $current_file -Destination $archive_path
}
catch
{
Write-Host ("file " + $files_to_retrieve[$i].Line + " not found")
}
$data = Get-Content $current_file
#do modifications here
}
The try-catch isn't working as expected. I have a file name in the file list that is not present in $some_path. I was expecting try-catch to stop the execution and do the Write-Host. Instead, it doesn't run the Write-Host and continues to the $data = Get-Content $current_file step, which is throwing a terminating error because the path doesn't exist for the missing file. How can I fix this?
Your first problem is the try/catch as you know. Taking a brief look at the documentation for about_Try_Catch_Finally you will see that..
Use Try, Catch, and Finally blocks to respond to or handle terminating
errors in scripts.
Your Copy-Item line is not throwing a terminating error. We fix that with the common parameter -ErrorAction
Copy-Item -Path $current_file -Destination $archive_path -ErrorAction Stop
So if there is a problem then the Catch block should be called. Assuming that was the real issue there.
You have another issue I think as well that might just be a typo. I see the following snippet more than once.
$file_list[$i].Line
Earlier you have declared $file_list as "D:\some_folder\file_list.txt" which is a string. I think what you meant to have is below. The above code would be null since string dont have a line property. But the return from Select-String can!
$files_to_retrieve[$i].Line
Related
Could you help me with a powershell script?
I want to check if multiple files exist, if they exist then delete the files.
Than provide information if the file has been deleted or information if the file does not exist.
I have found the script below, it only works with 1 file, and it doesn't give an message if the file doesn't exist.
Can you help me adjust this? I would like to delete file c:\temp\1.txt, c:\temp\2.txt, c:\temp\3.txt if they exist.
If these do not exist, a message that they do not exist. Powershell should not throw an error or stop if a file doesn't exist.
$FileName = "C:\Test\1.txt"
if (Test-Path $FileName) {
Remove-Item $FileName -verbose
}
Thanks for the help!
Tom
You can create a list of path which you want to delete and loop through that list like this
$paths = "c:\temp\1.txt", "c:\temp\2.txt", "c:\temp\3.txt"
foreach($filePath in $paths)
{
if (Test-Path $filePath) {
Remove-Item $filePath -verbose
} else {
Write-Host "Path doesn't exits"
}
}
Step 1: You want multiple files. You can do that two ways:
$files = "C:\file1.txt","C:\file2.txt","C:\file3.txt"
That would work, is cumbersome. Easier? Have all files in one .csv list, import that. Remember, the first row is not read, since its consider the header:
$files = Import-Csv "C:\yourcsv.csv"
Alright Step 2: now you got your files, now we want to cycle them:
Foreach ($file in $files) {
If (Test-Path $file) {
Remove-Item $file -verbose | Add-Content C:\mylog.txt }
else { Write-Host "$file not found" }}
Foreach loops take each individual "entry" in one variable and do whatever you want with them.
That should do what you want.
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 still very new and I have for example one script to backup some folders by zipping and copying them to a newly created folder.
Now I want to know if the zip and copy process was successful, by successful i mean if my computer zipped and copied it. I don't want to check the content, so I assume that my script took the right folders and zipped them.
Here is my script :
$backupversion = "1.65"
# declare variables for zip
$folder = "C:\com\services" , "C:\com\www"
$destPath = "C:\com\backup\$backupversion\"
# Create Folder for the zipped services
New-Item -ItemType directory -Path "$destPath"
#Define zip function
function create-7zip{
param([String] $folder,
[String] $destinationFilePath)
write-host $folder $destinationFilePath
[string]$pathToZipExe = "C:\Program Files (x86)\7-Zip\7zG.exe";
[Array]$arguments = "a", "-tzip", "$destinationFilePath", "$folder";
& $pathToZipExe $arguments;
}
Get-ChildItem $folder | ? { $_.PSIsContainer} | % {
write-host $_.BaseName $_.Name;
$dest= [System.String]::Concat($destPath,$_.Name,".zip");
(create-7zip $_.FullName $dest)
}
Now I can either check if in the parentfolder is a newly created folder by time.
Or i can check if there are zip folders in my subfolders I created.
What way would you suggest? I probably just know this ways, but there are a million way to do this.
Whats your idea? The only rule is , that powershell should be used.
thanks in advance
You could try using the Try and Catch method by wrapping the (create-7zip $_.FullName $dest) with a try and then catch any errors:
Try{ (create-7zip $_.FullName $dest) }
Catch{ Write-Host $error[0] }
This will Try the function create-7zip and write any the errors that many accrue to the shell.
One thing that can be tried is checking the $? variable for the status of the command.
$? stores the status of the last command run,
So for
create-7zip $_.FullName $dest
If you then echo out $? you will see either true or false.
Another option is the $error variable
You can also combine these in all sorts of ways (Or with the exception handling).
For example, run your command
foreach-object {
create-7zip $_.FullName $dest
if (!$?) {"$_.FullName $ErrorVariable" | out-file Errors.txt}
}
That script is more pseudocode for ideas than working code, but it should at least get you close to using it!
Currently using the following code to search a directory and copy any .msi that is found to another directory. I don't want to copy a .msi if it is in use by another program. I saw other questions on StackExchange that showed other ways to check if a file was in use but using a try/catch block around a Rename-Item command seemed like a simpler solution. If the script can't rename the item I want it go to the next $file in $listoffiles. The only way that I could get it to work was by using the code listed below. However I can't seem to format the resulting error message the way I want and it seems like -ErrorAction Stop shouldn't be necessary. If an error occurs in the try block shouldn't the error just go to the catch block? Also, only 2 of the 3 variables in my error message are written out to the log file.
Current code:
$listofFiles=(Get-ChildItem -Path $outputPath -Filter "*.msi" | where {$_.Name -notlike "*.Context.msi" -and $_.LastAccessTime -gt (Get-Date).AddMinutes(-15)})
foreach ($file in $listofFiles){
$y = ($file -split ("\\"))
$msiFolder = $y[4]
#Tests to see if $file currently in use
try{
Rename-Item -Path $file -NewName $file -ErrorVariable renameError -ErrorAction Stop
}
catch{
"$logTime ERROR Could not copy $msiFolder: $renameError.Exception.Message()" >> $copyLog
continue
}
#Code that copies files that are not in use.
Currently the error is displayed like so:
02/18/13 13:43:25 ERROR Could not copy System.Management.Automation.ActionPreferenceStopException: Command execution stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The process cannot access the file because it is being used by another process.
at System.Management.Automation.StatementListNode.ExecuteStatement(ParseTreeNode statement, Array input, Pipe outputPipe, ArrayList& resultList, ExecutionContext context)
at System.Management.Automation.StatementListNode.Execute(Array input, Pipe outputPipe, ArrayList& resultList, ExecutionContext context)
at System.Management.Automation.TryStatementNode.Execute(Array input, Pipe outputPipe, ArrayList& resultList, ExecutionContext context).Exception.Message()
I would like for it to look like:
02/18/13 13:34:34 ERROR Could not copy ABC_CLIENT: The process cannot access the file because it is being used by another process.
Questions
Why does $msiFolder not appear in the log message?
How do I get simplify the error message?
Is there a better way to use a try/catch block to break out of an iteration of the foreach loop?
Update 1:
I cleared $error and ran:
Rename-Item -Path $file -NewName $file -ErrorAction SilentlyContinue
if (!$?){"$logTime ERROR Could not copy $file: $error[0].Exception.Message()" >> $copyLog}
The result was:
02/18/13 14:52:59 ERROR Could not copy The process cannot access the file because it is being used by another process.[0].Exception.Message()
Why does $logTime get printed but the other variables do not?
Update 2:
Final code that I used:
Rename-Item -Path $file -NewName $file -ErrorAction SilentlyContinue
if (!$?){
"$logTime ERROR Could not copy ${msiFolder}: $($error[0].Exception.Message)" >> $copyLog
continue
}
If you want to use try/catch you need to convert the non-terminating error you get from Rename-Item to a terminating error. That is why you need to use -ErrorAction Stop to make your catch block get invoked. However, you can do this another way e.g.:
foreach ($file in $listofFiles){
$y = ($file -split ("\\"))
$msiFolder = $y[4]
#Tests to see if $file currently in use
Rename-Item -Path $file -NewName $file -ErrorVariable renameError -ErrorAction SilentlyContinue
if (!$?) {
"$logTime ERROR Could not copy ${msiFolder}: $($error[0].Exception.Message)" >> $copyLog
continue
}
#Code that copies files that are not in use.
}
The automatic variable $? indicates whether the last command succeeded or not.
As for why $msiFolder isn't getting output, I would check the split operation. Check that $y.count is >= 5.
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
}