I'm having a problem with a script.
Every night, the server task scheduler runs a batch that creates a log on which PCs it couldn't update. This PS script then pulls the error log and sends an email notifying us of the PCs. The error log file is a .txt file and the name of the file changes everyday to reflect the date, for example, 10172016.txt.
I can get the script to email and it used to pull the log file and place it into its body; but now, it's blank and shows an error regarding "+".
Code:
clear
$smtpserver = "test.test.org"
$from="noreply#test.org"
$to="adamh#test.org"
$subject="Update Error Log for" + " " + (get-date -format d)
$message = (Get-Content -path "\\testserver\Departments\IT\Private\test\logs" + "(get-date -f mmddyyyy)" + ".txt" | Out-String )
$mailer = new-object Net.Mail.SMTPclient($smtpserver)
$msg = new-object Net.Mail.MailMessage($from,$to,$subject,$body)
$msg.IsBodyHTML = $false
$mailer.send($msg)
Please help.
There is (at least) a problem with your code, namely (command simplified):
Get-Content -path "\path\to\" + "(get-date -f MMddyyyy)" + ".txt"
It needs to be (note the enclosing parentheses and the absence of double quotes around the get-date command):
Get-Content -path ("\path\to\" + (get-date -f MMddyyyy) + ".txt")
PowerShell has two fundamental parsing modes - argument mode and expression mode, and understanding which is applied when is important:
see Get-Help about_Parsing.
In short: Without (...), PowerShell sees "\path\to\" + (get-date -f MMddyyyy) + ".txt" as 5 separate arguments rather than a single, synthesized string.
Alternatively, you could use a single double-quoted string with string interpolation that uses the subexpression operator, $(...), to embed the get-date call:
Get-Content -path "\path\to\$(get-date -f MMddyyyy).txt"
Additional observations:
A native Send-MailMessage is available since PowerShell version 3.
The format string for 2-digit months is MM, not mm (the latter is minutes).
While using pseudo method syntax new-object Net.Mail.MailMessage($from,$to,$subject,$body) happens to work, it's better to avoid it, because it can mask a misconception of how New-Object works: The proper form is New-Object Net.Mail.MailMessage -ArgumentList $from,$to,$subject,$body; as you can see, because you're dealing with a cmdlet, it uses argument mode.
Related
Could you please help me with this script? I am writing in PowerShell a script in order to monitor data of one .csv file into PRTG, basically script goes to a remote server make a SFTP connection and brings it to PRTG server.
Then I just look for the data, the issue is that I have is to select and specific file, for example in the remote server there is a new file with and specific date generated every hour and some values are without a pattern.
I already tested and script works except, to look for specific file, cause if I put static name file works
These are examples of .csv files that i need
structure is name + date (Y-m-d-H) + 8 characters +.csv:
RG_400_Total_by_Hour-20210606021446-174.csv
RG_400_Total_by_Hour-20210606031630-456.csv
RG_400_Total_by_Hour-20210606041445-288.csv
RG_400_Total_by_Hour-20210606051513-761.csv
RG_400_Total_by_Hour-20210606061446-877.csv
RG_400_Total_by_Hour-20210606071446-454.csv
RG_400_Total_by_Hour-20210606081446-838.csv
RG_400_Total_by_Hour-20210606091505-171.csv
RG_400_Total_by_Hour-20210606101447-220.csv
This is the part where I completely stuck.
How can I use a wildcard in this variable that I'm trying:
$file=$remotepath+"/RG_400_Total_by_Hour-"+$date+""+$WILDCARD???+""
Here is a part of the script
$date= get-date -UFormat "%Y%m%d%H"
$t= Look for wildcart to use
$folder=$date
$hora= [int] (get-date (get-date).addhours(-2) -UFormat "%H")
$ayer = (get-date (get-date).addDays(-1) -UFormat "%Y%m%d")
if ($hora -eq 22) {$folder=$ayer}
if ($hora -eq 23) {$folder=$ayer}
$remotepath= $remotedir
$remotedir="/usr/local/sandvine/var/nds/cache/csv"
$file=$remotepath+"/RG_400_Total_by_Hour-"+$b+""+$t+""
$file2=$localpath+"/RG_400_Total_by_Hour-"+$b+""+$t+""
Get-SFTPFile -SFTPsession $global:Session -remotefile $file -localpath $localpath
Remove-SFTPSession -sftpsession $global:Session | Out-Null
I really appreciate any comment, thanks guys.
I'm working on a script that takes a directory name as input, and writes a log file to that directory.
This works in most cases:
param([string]$output_folder)
$logfile="$output_folder" + "\myscript.log"
Write-Output "logfile: " $logfile
(Get-Date -Format "[yyyy-MM-dd HH:mm:ss]") | Out-file $logfile -Append
except when the file path contains a wildcard:
E:\Publishing\User manual [en]\
This results in an error 'Cannot perform operation because the wildcard path E:\Publishing\User manual [en]\myscript.log did not resolve to a file.
I spent some time trying to sanitize the input, but I haven't found a way that works yet.
$logfile='$output_folder' + "\myscript.log"
This treats the string as a literal, but that prevents $output_folder from being expanded.
param([io.directoryinfo]$output_folder)
doesn't help either.
Edit:
A comment suggested this:
Out-file -LiteralPath $logfile -Append
That works for the instances where I use Out-file.
I also call an external program, and I can't use -LiteralPath here:
"C:\Program Files\Saxonica\SaxonHE9.8N\bin\Transform.exe" -s:"$inputFile" -xsl:"$output_folder\$transformation_filename" -o:"$outputFile" -t 1>>$logfile 2>&1
in fact I have more arguments here that need to be treated as literals. Is there a way so specify this when I create the string?
$logfile="$output_folder" + "\myscript.log"
I need to collect the standard output and error log from several processes into one single log file.
So every output must append to this log file.
I want to call all the jobs with lines like this:
$p=start-process myjob.bat -redirectstandardoutput $logfile -redirecterroroutput $logfile -wait
Where do I have to put the information to append?
In order to append to a file you'll need to use a slightly different approach. You can still redirect an individual process' standard error and standard output to a file, but in order to append it to a file you'll need to do one of these things:
Read the stdout/stderr file contents created by Start-Process
Not use Start-Process and use the call operator, &
Not use Start-Process and start the process with .NET objects
The first way would look like this:
$myLog = "C:\File.log"
$stdErrLog = "C:\stderr.log"
$stdOutLog = "C:\stdout.log"
Start-Process -File myjob.bat -RedirectStandardOutput $stdOutLog -RedirectStandardError $stdErrLog -wait
Get-Content $stdErrLog, $stdOutLog | Out-File $myLog -Append
The second way would look like this:
& myjob.bat 2>&1 >> C:\MyLog.txt
Or this:
& myjob.bat 2>&1 | Out-File C:\MyLog.txt -Append
The third way:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "myjob.bat"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$output = $p.StandardOutput.ReadToEnd()
$output += $p.StandardError.ReadToEnd()
$output | Out-File $myLog -Append
Like Unix shells, PowerShell supports > redirects with most of the variations known from Unix, including 2>&1 (though weirdly, order doesn't matter - 2>&1 > file works just like the normal > file 2>&1).
Like most modern Unix shells, PowerShell also has a shortcut for redirecting both standard error and standard output to the same device, though unlike other redirection shortcuts that follow pretty much the Unix convention, the capture all shortcut uses a new sigil and is written like so: *>.
So your implementation might be:
& myjob.bat *>> $logfile
Andy gave me some good pointers, but I wanted to do it in an even cleaner way. Not to mention that with the 2>&1 >> method PowerShell complained to me about the log file being accessed by another process, i.e. both stderr and stdout trying to lock the file for access, I guess. So here's how I worked it around.
First let's generate a nice filename, but that's really just for being pedantic:
$name = "sync_common"
$currdate = get-date -f yyyy-MM-dd
$logfile = "c:\scripts\$name\log\$name-$currdate.txt"
And here's where the trick begins:
start-transcript -append -path $logfile
write-output "starting sync"
robocopy /mir /copyall S:\common \\10.0.0.2\common 2>&1 | Write-Output
some_other.exe /exeparams 2>&1 | Write-Output
...
write-output "ending sync"
stop-transcript
With start-transcript and stop-transcript you can redirect ALL output of PowerShell commands to a single file, but it doesn't work correctly with external commands. So let's just redirect all the output of those to the stdout of PS and let transcript do the rest.
In fact, I have no idea why the MS engineers say they haven't fixed this yet "due to the high cost and technical complexities involved" when it can be worked around in such a simple way.
Either way, running every single command with start-process is a huge clutter IMHO, but with this method, all you gotta do is append the 2>&1 | Write-Output code to each line which runs external commands.
Maybe it is not quite as elegant, but the following might also work. I suspect asynchronously this would not be a good solution.
$p = Start-Process myjob.bat -redirectstandardoutput $logtempfile -redirecterroroutput $logtempfile -wait
add-content $logfile (get-content $logtempfile)
I am trying to pass a .txt file with arguments to an .exe file via powershell. Currently, this is what I have.
Write-Host "starting upgrade at $(Get-Date -format 'U')"
C:\dev\temp.exe.exe /DIR="C:\TEST" /BTPServerHost="Test" /DBInstance="testDB" /Log=C:\path\to\test\testlog.txt
This is calling a function within an InnoScript file that accepts command line input.
How would I format the .txt file, and how would I be able to pass it into the .exe? Any help would be appreciated! Thanks!
If you are saying, in this text file, there are just these argument line on individual rows and you are saying you've already tried something like the below and were not successful?
You also don't need the Write-Host for the message line, since the default is output to screen. You normal only need Write-Host for colorizing screen text, and a few other formatting cases, depending on what you are doing. All-in-All, Write-Host should be avoided.
"starting upgrade at $(Get-Date -format 'U')"
($ConsoleCommand = Get-Content -Path 'd:\temp\input.txt' -Raw)
# Results - showing the commands in the file before process them
whoami
get-date
'hello world'
Without using the -Wait switch, this will spawn 3 separate PowerShell consoles with the results
ForEach($CmdLine in $ConsoleCommand)
{ Start-Process -FilePath powershell -ArgumentList "-NoExit","-Command &{ $CmdLine }" }
you can of course point to your .exe vs what I am doing here.
Start-Process
By adding the -Raw after specifying the .txt file path it ignores newline characters and returns the entire contents of a file in one string with the newlines preserved. By default, newline characters in a file are used as delimiters to separate the input into an array of strings.
This script takes parameters from a txt file and passes them into an executable and auto-populates the fields in the installation wizard. This is what I'm looking to do, but I don't want to start a new process for each argument in the input txt file.
Write-Host "starting upgrade at $(Get-Date -format 'U')" Get-Content -Path C:\TestInput.txt -Raw | foreach {Start-Process C:\dev\test.exe -ArgumentList $_}
The TestInput.txt file passed in looks like this:
/DIR="C:\TEST"
/ServerHost="Test"
/DBInstance="testDB"
/Log=C:\testlog.txt
I'm trying to output a CSV in the current directory, but in a folder that matches the $ComputerName, which already exists. I'm doing this for a list of machines regularly and rather than manually put them in their folder it would be awesome to do it in the script.
Here is the current code, writing to the script directory.
#writing file to
[Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory + " Named the following.")
Write-Host $PritnersFilename
I've tried adding $ComputerName to various locations and had no luck.
Examples:
Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory\$ComputerName + " Named the following.")
Write-Host ("Saving CSV Files at " + [Environment]::(CurrentDirectory\$ComputerName) + " Named the following.")
EDIT: $ComputerName is the variable of the target, not the local host
It would be easier if I saw the whole code. But I made up an example because I felt it would be easier to explain it likes this since I don't know where you get your variables from.
Pretty straight forward, it loops through the computers and if there isn't a folder in current folder named $computername it creates one. Then your export code comes in where it exports computer data to that folder we just created.
Key part: Using ".\" is the same thing as current folder.
cd C:\Scriptfolder\
# computer variables for example
$computers = #()
$computers += "HOST1"
$computers += "HOST2"
$computers += "HOST3"
# looping through all objects
Foreach($computer in $computers){
# creating folder named after computername if one doesn't exist
if(!(Test-Path ".\$computer")){
New-Item -ItemType Directory -Name $computer -Path ".\"
}
# output path with computername in it
$outputpath = ".\$computer\output.csv"
# your export code
$computer | Export-CSV $outputpath
}
[Environment]::CurrentDirectory\$ComputerName, due to being inside (...) and being used as an operand of the + operator, is parsed in expression mode, which causes a syntax error.
For an overview of PowerShell's parsing modes, see this answer of mine.
You need "..." (an expandable string) to perform your string concatenation, using subexpression operator $(...) to embed expression [Environment]::CurrentDirectory and embedding a reference to variable $ComputerName directly.
"$([Environment]::CurrentDirectory)\$ComputerName"
For an overview of string expansion (string interpolation) in PowerShell,
see this answer of mine.
Alternatively, you could use an expression with + as well (or even mix the two approaches):
# Enclose the whole expression in (...) if you want to use it as a command argument.
[Environment]::CurrentDirectory + '\' + $ComputerName
Note: The most robust (albeit slower) method for building filesystem paths is to use the Join-Path cmdlet:
# Enclose the whole command in (...) to use it as part of an expression.
Join-Path ([Environment]::CurrentDirectory) $ComputerName
Note the need for (...) around [Environment]::CurrentDirectory to ensure that it is recognized as an expression. Otherwise, since the command is parsed in argument mode, [Environment]::CurrentDirectory would be treated as a literal.