powershell result of 'if condition' write to a outfile - powershell

In powershell i am writing a script using 'if' condition to check a folder for files received in last 2 hours. The code works fine and the output is written to the screen, instead i want it to write to a file which can be emailed.
Request for kind help.
Regards
Abhijeet
EDIT: Code
$f = 'D:\usr\for_check'
$files = ls $f
Foreach ($file in $files)
{
$createtime = $file.CreationTime
$nowtime = get-date
if (($nowtime - $createtime).totalhours -le 2)
{
"$file"
}
}

You can either use the redirection operator > or Out-File
Examples:
"abc" > c:\out.txt
"abc" | Out-File c:\out.txt

Your code is way too complicated. Something like this would be more PoSh:
$src = "D:\usr\for_check"
$out = "C:\output.txt"
$append = $false
Get-ChildItem $src | ? {
$_.CreationTime -ge (Get-Date).AddHours(-2)
} | % { $_.Name } | Out-File $out -Append:$append

You will want to use the >> operator instead of > or out-file operators as they will overwrite the file every time it's used. Whereas the >> operator will write to the file on the next line.
Example:
$file >> c:\out.txt

Writing each line to the file inside the loop can cause a lot of disk I/O.
You can wrap the loop in a script block, and then output all the lines to the file in one write operation.
$f = 'D:\usr\for_check'
$files = ls $f
&{Foreach ($file in $files)
{
$createtime = $file.CreationTime
$nowtime = get-date
if (($nowtime - $createtime).totalhours -le 2)
{
"$file"
}
}
} | set-content c:\outfile.tx

Related

Powershell/ Print by filename

My English may not be perfect but I do my best.
I'm trying to write a Powershell script where the filename has a number at the end and it should print exactly that often.
Is this somehow possible ?
With the script it prints it only 1 time.
For whatever reason..
param (
[string]$file = "C:\Scans\temp\*.pdf",
[int]$number_of_copies = 1
)
foreach ($onefile in (Get-ChildItem $file -File)) {
$onefile -match '\d$' | Out-Null
for ($i = 1; $i -le [int]$number_of_copies; $i++) {
cmd /C "lpr -S 10.39.33.204 -P optimidoc ""$($onefile.FullName)"""
}
}
There is no need for parameter $number_of_copies when the number of times it should be printed is taken from the file's BaseName anyway.
I would change your code to:
param (
[string]$path = 'C:\Scans\temp'
)
Get-ChildItem -Path $path -Filter '*.pdf' -File |
# filter only files that end with a number and capture that number in $matches[1]
Where-Object { $_.BaseName -match '(\d+)$' } |
# loop through the files and print
ForEach-Object {
for ($i = 1; $i -le [int]$matches[1]; $i++) {
cmd /C "lpr -S 10.39.33.204 -P optimidoc ""$($_.FullName)"""
}
}
Inside the ForEach-Object, on each iteration, the $_ automatic variable represents the current FileInfo object.
P.S. Your script prints each file only once because you set parameter $number_of_copies to 1 as default value, but the code never changes that to the number found in the file name.
BTW. Nothing wrong with your English

powershell: delete specific line from x to x

I'm new in powershell and I absolutely dont get it ...
Just want to delete line 7 to 2500 of a text file. First 6 lines should be untouched.
With linux bash everything is so easy, just:
sed -i '7,2500d' $file
Did not find any solution for mighty powershell :-(
Thank you.
Use Get-Content to read the contents of the file into a variable. The variable can be indexed like a regular PowerShell array. Get the parts of the array you need then pipe the variable into Set-Content to write back to the file.
$file = Get-Content test.log
$keep = $file[0..1] + $file[7..($file.Count - 1)]
$keep | Set-Content test.log
Using this as the contents of the file test.log:
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
This script will output the following into test.log (overwriting the contents):
One
Two
Eight
Nine
In your case, you will want to use $file[0..5] + $file[2500..($file.Count - 1)].
To remove a series of lines in a text file, you could do something like this:
$fileIn = 'D:\Test\File1.txt'
$fileOut = 'D:\Test\File2.txt'
$startRemove = 7
$endRemove = 2500
$currentLine = 1
# needs .NET 4
$newText = foreach ($line in [System.IO.File]::ReadLines($fileIn)) {
if ($currentLine -lt $startRemove -or $currentLine -gt $endRemove) { $line}
$currentLine++
}
$newText | Set-Content -Path $fileOut -Force
Or, if your version of .NET is below 4.0
$reader = [System.IO.File]::OpenText($fileIn)
$newText = while($null -ne ($line = $reader.ReadLine())) {
if ($currentLine -lt $startRemove -or $currentLine -gt $endRemove) { $line }
$currentLine++
}
$reader.Dispose()
$newText | Set-Content -Path $fileOut -Force
Select-object -index takes an array, so:
1..10 > file
(get-content file) | select -index (0..5) | set-content file
get-content file
1
2
3
4
5
6
Or:
(cat file)[0..5] | set-content file

Looping through File Groups such as FileGroup159, FileGroup160, etc. in Powershell

So I got the code to work how I like it for individual files. Based on some of the suggestions below, I was able to come up with this:
$Path = "C:\Users\User\Documents\PowerShell\"
$Num = 160
$ZipFile = "FileGroup0000000$Num.zip"
$File = "*$Num*.txt"
$n = dir -Path $Path$File | Measure
if($n.count -gt 0){
Remove-Item $Path$ZipFile
Compress-Archive -Path $Path$File -DestinationPath $Path
Rename-Item $Path'.zip' $Path'FileGroup0000000'$Num'.zip'
Remove-Item $Path$File
}
else {
Write-Output "No Files to Move for FileGroup$File"
}
The only thing I need to do now is have $Num increment after the program finishes each time. Therefore the program will run, and then move $Num to 160, 161, etc. and I will not have to re initiate the code manually. Thanks for the help so far.
Your filename formatting should go inside the loop and you should use the Format operator -f to get the preceding zeros, like:
159..1250 | ForEach-Object {
$UnzippedFile = 'FileGroup{0:0000000000}' -f $_
$ZipFile = "$UnzippedFile.zip"
Write-Host "Unzipping: $ZipFile"
# Do your thing here
}

Monitor a command and wait for it to complete before proceeding to next command?

I have written a PowerShell script that will:
grab all txt files from a directory
perform a line-by-line assessment of the first file (grabbing headers and appending, appending data to each line in file, saving to an output file)
for subsequent files, grab body (excluding header), append data, then add to output file
The problem is in the use of Add-Content where the process hangs so certain files don't get written because the output file is in use. I added a function (based on recommendations found in various places on StackExchange) that test the output file to determine if it is available for read-write. This seems like a 'brute-force' approach.
Is there a way to monitor the actual Add-Content process launched by PowerShell to identify when it is complete? Or is there some other way to disaggregate the code as written to use the process control commands in PowerShell?
Sample:
function IsFileAccessible([String]$FullFileName) {
[Boolean]$IsAccessible = $false
try {
[IO.File]::OpenWrite($FullFileName).Close();
$IsAccessible = $true
} catch {
$IsAccessible = $false
}
return $IsAccessible
}
cd '[filepath]'
del old_output.type
$filearray = #()
$files = Get-ChildItem '[filepath]' -Filter "*.txt"
$outfile = 'new_output.type'
for ($i=0; $i -lt $files.Count; $i++) {
# Define variables
$lastWriteTime = $files[$i].LastWriteTime
# Define process steps for appending data
filter Add-Time {"$_$lastWriteTime"}
if ($i -eq 0) {
$lines = Get-Content $files[$i]
for ($j=0;$j -lt $lines.Count; $j++) {
if ($j -eq 0) {
$appended_txt = 'New_Header'
filter Add-Header{"$_$appended_txt"}
$lines[$j] | Add-Header | Add-Content $outfile
} else {
$lines[$j] | Add-Time | Add-Content $outfile
}
}
} else {
do {
$ErrorActionPreference = 'SilentlyContinue'
$test = IsFileAccessible('[filepath-new_output.type]')
echo 'file open'
} until ($test -eq 'True')
$ErrorActionPreference = 'Continue'
echo 'okay'
(Get-Content $files[$i].FullName | Select-Object -Skip 1) |
Add-Time | Add-Content $outfile
}
}

Remove Top Line of Text File with PowerShell

I am trying to just remove the first line of about 5000 text files before importing them.
I am still very new to PowerShell so not sure what to search for or how to approach this. My current concept using pseudo-code:
set-content file (get-content unless line contains amount)
However, I can't seem to figure out how to do something like contains.
While I really admire the answer from #hoge both for a very concise technique and a wrapper function to generalize it and I encourage upvotes for it, I am compelled to comment on the other two answers that use temp files (it gnaws at me like fingernails on a chalkboard!).
Assuming the file is not huge, you can force the pipeline to operate in discrete sections--thereby obviating the need for a temp file--with judicious use of parentheses:
(Get-Content $file | Select-Object -Skip 1) | Set-Content $file
... or in short form:
(gc $file | select -Skip 1) | sc $file
It is not the most efficient in the world, but this should work:
get-content $file |
select -Skip 1 |
set-content "$file-temp"
move "$file-temp" $file -Force
Using variable notation, you can do it without a temporary file:
${C:\file.txt} = ${C:\file.txt} | select -skip 1
function Remove-Topline ( [string[]]$path, [int]$skip=1 ) {
if ( -not (Test-Path $path -PathType Leaf) ) {
throw "invalid filename"
}
ls $path |
% { iex "`${$($_.fullname)} = `${$($_.fullname)} | select -skip $skip" }
}
I just had to do the same task, and gc | select ... | sc took over 4 GB of RAM on my machine while reading a 1.6 GB file. It didn't finish for at least 20 minutes after reading the whole file in (as reported by Read Bytes in Process Explorer), at which point I had to kill it.
My solution was to use a more .NET approach: StreamReader + StreamWriter.
See this answer for a great answer discussing the perf: In Powershell, what's the most efficient way to split a large text file by record type?
Below is my solution. Yes, it uses a temporary file, but in my case, it didn't matter (it was a freaking huge SQL table creation and insert statements file):
PS> (measure-command{
$i = 0
$ins = New-Object System.IO.StreamReader "in/file/pa.th"
$outs = New-Object System.IO.StreamWriter "out/file/pa.th"
while( !$ins.EndOfStream ) {
$line = $ins.ReadLine();
if( $i -ne 0 ) {
$outs.WriteLine($line);
}
$i = $i+1;
}
$outs.Close();
$ins.Close();
}).TotalSeconds
It returned:
188.1224443
Inspired by AASoft's answer, I went out to improve it a bit more:
Avoid the loop variable $i and the comparison with 0 in every loop
Wrap the execution into a try..finally block to always close the files in use
Make the solution work for an arbitrary number of lines to remove from the beginning of the file
Use a variable $p to reference the current directory
These changes lead to the following code:
$p = (Get-Location).Path
(Measure-Command {
# Number of lines to skip
$skip = 1
$ins = New-Object System.IO.StreamReader ($p + "\test.log")
$outs = New-Object System.IO.StreamWriter ($p + "\test-1.log")
try {
# Skip the first N lines, but allow for fewer than N, as well
for( $s = 1; $s -le $skip -and !$ins.EndOfStream; $s++ ) {
$ins.ReadLine()
}
while( !$ins.EndOfStream ) {
$outs.WriteLine( $ins.ReadLine() )
}
}
finally {
$outs.Close()
$ins.Close()
}
}).TotalSeconds
The first change brought the processing time for my 60 MB file down from 5.3s to 4s. The rest of the changes is more cosmetic.
$x = get-content $file
$x[1..$x.count] | set-content $file
Just that much. Long boring explanation follows. Get-content returns an array. We can "index into" array variables, as demonstrated in this and other Scripting Guys posts.
For example, if we define an array variable like this,
$array = #("first item","second item","third item")
so $array returns
first item
second item
third item
then we can "index into" that array to retrieve only its 1st element
$array[0]
or only its 2nd
$array[1]
or a range of index values from the 2nd through the last.
$array[1..$array.count]
I just learned from a website:
Get-ChildItem *.txt | ForEach-Object { (get-Content $_) | Where-Object {(1) -notcontains $_.ReadCount } | Set-Content -path $_ }
Or you can use the aliases to make it short, like:
gci *.txt | % { (gc $_) | ? { (1) -notcontains $_.ReadCount } | sc -path $_ }
Another approach to remove the first line from file, using multiple assignment technique. Refer Link
$firstLine, $restOfDocument = Get-Content -Path $filename
$modifiedContent = $restOfDocument
$modifiedContent | Out-String | Set-Content $filename
skip` didn't work, so my workaround is
$LinesCount = $(get-content $file).Count
get-content $file |
select -Last $($LinesCount-1) |
set-content "$file-temp"
move "$file-temp" $file -Force
Following on from Michael Soren's answer.
If you want to edit all .txt files in the current directory and remove the first line from each.
Get-ChildItem (Get-Location).Path -Filter *.txt |
Foreach-Object {
(Get-Content $_.FullName | Select-Object -Skip 1) | Set-Content $_.FullName
}
For smaller files you could use this:
& C:\windows\system32\more +1 oldfile.csv > newfile.csv | out-null
... but it's not very effective at processing my example file of 16MB. It doesn't seem to terminate and release the lock on newfile.csv.