Powershell start-job scriptblock not executing - powershell

I have some pretty simply code which aims to convert each file in a directory into HTML. My problem is that although a job is successfully created for each file, the scriptblock does not run, ever.
$convert = {
Param(
[parameter(ValueFromPipeline=$true)]
$file
)
$content = Get-Content -Path $file.FullName
$outDir= New-Item -Type dir -Path $file.FullName + "\HTMLFiles"
$outFile = $outDir + $file.Name + ".html"
foreach($line in $content) {
#move the content into a variable and add some html tags
$html = $html + '<tr>' + $line + '</tr>' +'<br>'
}
#convert the variable to .html and save the result as a file
ConvertTo-Html -Head $style -Body $html | Out-File -FilePath $outFile -Encoding "ASCII"
#empty the variable
$html = " "
}
Function main
{
Param(
[parameter(Position=0, Mandatory=$true, ValueFromPipeLine=$true)]
$target = $args[0]
)
#stores some html styling code
$path = $pwd.Path + "\style.txt"
$style = Get-Content -Path $path
#collect all files in the dirctory
$files = Get-ChildItem -Path $target -Recurse
foreach($file in $files) {
#for each file in the collection start a job which runs the given scriptblock (scriptblock is not working)
Start-Job -Name $file.name -ScriptBlock $convert -ArgumentList $file
}
#clean-up
Write-Host "Finished jobs"
Wait-Job *
Remove-Job -State Completed
}
main($args[0])
I'm fairly new to powershell and have played around with ways to solve this, but can't seem to figure it out.

Change: I removed the parameters from the script block and the function.
Why: because the argument is passed in by Start-Job, and also for the function via the syntax function name (argument1, argument2) {}.
I also took away from the brackets from the function call, because in Powershell you call functions like:
function "argument1" "argument2"
$convert = {
$content = Get-Content -Path $file.FullName
$outDir= New-Item -Type dir -Path $file.FullName + "\HTMLFiles"
$outFile = $outDir + $file.Name + ".html"
foreach($line in $content) {
#move the content into a variable and add some html tags
$html = $html + '<tr>' + $line + '</tr>' +'<br>'
}
#convert the variable to .html and save the result as a file
ConvertTo-Html -Head $style -Body $html | Out-File -FilePath $outFile -Encoding "ASCII"
#empty the variable
$html = " "
}
Function main ($args)
{
$target = $args
#stores some html styling code
$path = $pwd.Path + "\style.txt"
$style = Get-Content -Path $path
#collect all files in the dirctory
$files = Get-ChildItem -Path $target -Recurse
foreach($file in $files) {
#for each file in the collection start a job which runs the given scriptblock (scriptblock is not working)
Start-Job -Name $file.name -ScriptBlock $convert -ArgumentList $file
}
#clean-up
Write-Output "Finished jobs"
Wait-Job *
Remove-Job -State Completed
}
main $args[0]

Related

For loop with list not working as expected Powershell

I'm trying to create a powershell script which checks a log file for lines of text and if the line exists restarts a service and resets/archives the log. I got it working before with 1 "checkstring" if you will, but I've been struggling to get it to work with a list of strings. Could anyone help me figure out where I'm going wrong?
This is the code I'm currently using:
$serviceName = "MySQL80"
$file = "test.txt"
$pwd = "C:\tmp\"
$checkStrings = New-Object System.Collections.ArrayList
# Add amount of checkstrings
$checkStrings.add("Unhandled error. Error message: Error retrieving response.")
$checkStrings.add("Unhandled error. Error message: Error retrieving response. Second")
$logName = "ServiceCheck.log"
$backupFolder = "Archive"
$logString = (Get-Date).ToString("ddMMyyyyHHmmss"), " - The service has been reset and the log moved to backup" -Join ""
Set-Location -Path $pwd
if(Test-Path -Path $file) {
if(Test-Path -Path $backupFolder) {
} else {
New-Item -Path $pwd -Name $backupFolder -ItemType "director"
}
foreach ($element in $checkStrings) {
$containsWord = $fileContent | %{$_ -match $element}
if ($containsWord -contains $true) {
Restart-Service -Name $serviceName
$backupPath = $pwd, "\", $backupFolder, "\", $date, ".log" -join ""
$currentFile = $pwd, "\", $file -join ""
Copy-Item $currentFile -Destination $backupPath
Get-Content $currentFile | select-string -pattern $checkString -notmatch | Out-File $currentFile
if(Test-Path -Path $logName) {
Add-Content $logName $logString
} else {
$logString | Out-File -FilePath $logName
}
}
}
}

Concatenating Output from Folder

I have thousands of PDF documents that I am trying to comb through and pull out only certain data. I have successfully created a script that goes through each PDF, puts its content into a .txt, and then the final .txt is searched for the requested information. The only part I am stuck on is trying to combine all the data from each PDF into this .txt file. Currenly, each successive PDF simply overwrites the previous data and the search is only performed on the final PDF in the folder. How can I alter this set of code to allow each bit of information to be concatenated into the .txt instead of overwriting?
$all = Get-Childitem -Path $file1 -Recurse -Filter *.pdf
foreach ($f in $all){
$outfile = -join ', '
$text = convert-PDFtoText $outfile
}
Here is my entire script for reference:
Start-Process powershell.exe -Verb RunAs {
function convert-PDFtoText {
param(
[Parameter(Mandatory=$true)][string]$file
)
Add-Type -Path "C:\ps\itextsharp.dll"
$pdf = New-Object iTextSharp.text.pdf.pdfreader -ArgumentList $file
for ($page = 1; $page -le $pdf.NumberOfPages; $page++){
$text=[iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($pdf,$page)
Write-Output $text
}
$pdf.Close()
}
$content = Read-Host "What are we looking for?: "
$file1 = Read-Host "Path to search: "
$all = Get-Childitem -Path $file1 -Recurse -Filter *.pdf
foreach ($f in $all){
$outfile = $f -join ', '
$text = convert-PDFtoText $outfile
}
$text | Out-File "C:\ps\bulk.txt"
Select-String -Path C:\ps\bulk.txt -Pattern $content | Out-File "C:\ps\select.txt"
Start-Sleep -Seconds 60
}
Any help would be greatly appreciated!
To capture all output across all convert-PDFtoText in a single output file, use a single pipeline with the ForEach-Object cmdlet:
Get-ChildItem -Path $file1 -Recurse -Filter *.pdf |
ForEach-Object { convert-PDFtoText $_.FullName } |
Out-File "C:\ps\bulk.txt"
A tweak to your convert-PDFtoText function would allow for a more concise and efficient solution:
Make convert-PDFtoText accept Get-ChildItem input directly from the pipeline:
function convert-PDFtoText {
param(
[Alias('FullName')
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string] $file
)
begin {
Add-Type -Path "C:\ps\itextsharp.dll"
}
process {
$pdf = New-Object iTextSharp.text.pdf.pdfreader -ArgumentList $file
for ($page = 1; $page -le $pdf.NumberOfPages; $page++) {
[iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($pdf,$page)
}
$pdf.Close()
}
}
This then allows you to simplify the command at the top to:
Get-ChildItem -Path $file1 -Recurse -Filter *.pdf |
convert-PDFtoText |
Out-File "C:\ps\bulk.txt"

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

Loop in powershell

Hello Guys need some help, tips with script:
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
$missingi = "$path\$server.txt"
$plik = get-content $missingi
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
At first I create file with name of server, for example server.txt in this server we have list of KBs. Scripts searching in folder paczki that KB exist if yes copying this in server and create .bat
I would like do add automatically searching all .txt files eg server.txt, & server1.txt and use it in loop, I thought about something like that:
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
and put it in loop but its not really working, I try to add loop in this place:
for ($i in pliki_txt)
$path = ".\" # path do txt
$server="server" # server.txt
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
(....)
What am I doing wrong? Is there any easier way? Script is only working when I put manually set $server like $server="serwer"
You can try this:
$path = ".\"
Get-ChildItem $path -Filter *.txt | %{
$content = Get-content $_.FullName
Foreach($server in $content){
write-host $server
}
}
If I got that right, the issue here is that you're not putting the lines in the right order.
From your original code I would change the following
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
# $missingi = "$path\$server.txt"
$missingi = Get-ChildItem -Path $path -Filter server*.txt | Select -ExpandProperty Name
foreach ($m in $missingi) {
$plik = get-content $m
( ... )
}
That way you'll check every server*.txt file in that path and process it accordingly.
Or you could even turn it into a parameterized script like this
Param(
[Parameter(Mandatory = $true)]
[String]$path,
[Parameter(Mandatory = $true)]
[String]$pattern,
[Parameter(Mandatory = $true)]
[String]$packzi
)
$missingi = Get-ChildItem -Path $path -Filter *.txt | Select -ExpandProperty | Select-String "$pattern"
foreach ($m in $missingi) {
$plik = get-content $m
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
}
Then you would run it like this:
.\YourScript.ps1 -path ".\" -pattern "server" -packzi ".\packzi\"
That will give you more flexibility if you want to change the source path, the name pattern or the search patch.
I hope this helps.

PowerShell passing parameter with Invoke-Command

I can't seem to find the correct syntax to pass 2 variables from the CALL-script to the execution script in order to have it executed on the remote server. I tried single quotes, double quotes, brackets, .. nothing I can fiind passes the $Target and $OlderThanDays parameters to the script.
Thank you for your help.
The CALL-Script:
#================= VARIABLES ==================================================
$ScriptDir = "\\Server\Scripts"
#================= BODY =======================================================
# Invoke-Command -ComputerName SERVER1 -FilePath $ScriptDir\"Auto_Clean.ps1"
Invoke-Command -FilePath .\Test.ps1 -ComputerName SERVER01 -ArgumentList {-Target ´E:\Share\Dir1\Dir2´,-OlderThanDays ´10´}
The execution Script:
#================= PARAMETERS =================================================
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Target,
[Parameter(Mandatory=$True,Position=2)]
[string]$OlderThanDays
)
#================= BODY =======================================================
# Set start time & logname
$StartTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$LogName = "Auto_Clean.log"
# Format header for log
$TimeStamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()+" |"
$Header = "`n$TimeStamp Deleting files and folders that are older than $OlderThanDays days:`n"
Write-Output "$Header" # to console
Out-File $Target\$LogName -inputobject $Header -Append # to log
# PS 2.0 Workaround (`tee-object -append`) // PS 4.0: `Write-Output "`nDeleting folders that are older than $OlderThanDays days:`n" | Tee-Object $LogFile -Append`
# Remove files older than
Get-ChildItem -Path $Target -Exclude $LogName -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If folder can't be removed
if (Test-Path $Item)
{ "$Timestamp | FAILLED: $Item (IN USE)" }
else
{ "$Timestamp | REMOVED: $Item" }
} | Out-File $Target\$LogName -Append
# PS 4.0: ´| Tee-Object $Target\$LogName -Append` # Output folder names to console & logfile at the same time
# Remove empty folders
while (Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Test-Path) {
Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Remove-Item
}
# Format footer
$EndTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$TimeTaken = New-TimeSpan -Start $StartTime -End $EndTime
Write-Output ($Footer = #"
Start Time : $StartTime
End Time : $EndTime
Total Runtime : $TimeTaken
$("-"*79)
"#)
# Write footer to log
Out-File -FilePath $Target\$LogName -Append -InputObject $Footer
# Clean up variables
$Target=$StartTime=$EndTime=$OlderThanDays = $null
The execution script:
you have got to use " or ' but not ´ :
-argumentlist #('E:\Share\Dir1\Dir2',10)