I'm writing a PowerShell script to add / remove people from a distribution group. I want to send a message if the action was successful and another if it failed. This is part of the script:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
}
else {
Invoke-Expression $COMMAND
}
}
inputfile.txt, for the sake of information is:
jdoe#example.com,Add
jsmith#example.com,Remove
I've tried using $? and $lasExitCode but neither of those worked as expected as they only consider the output of "Invoke-Expression" and that is always successful.
What I am expecting is:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
else {
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
}
$? won't work because even if the command fails, Invoke-Expression was invoked successfully.
Use the & call operator to invoke the call directly instead, and $? will work. For the conditional parameter argument, use splatting!
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$user,$action,$null = $x.Split(',')
# construct command name
$command = "$ACTION-DistributionGroupMember"
# organize the parameter arguments in a hashtable for splatting later
$paramArgs = #{
Identity = 'Group Name'
Member = $USER
}
# conditionally add the `-Confirm` parameter
if ($ACTION -ieq "remove") {
$paramArgs['Confirm'] = $false
}
# invoke the command with the call operator
& $command #paramArgs
if($?){
# command invocation suceeded
}
else {
# command invocation failed
}
}
Related
I have a PowerShell script that calls plink.exe regularly. Normally, the two output lines about keyboard-interactive prompts are simply annoying.
However, when run using Start-Job, they get output as error text as soon as I call Receive-Job.
Is there any way to suppress these? I'd rather not suppress all errors.
My test code:
$test_scriptblock = [scriptblock]::Create(#"
param(
`$argumentlist
)
`$pw = `$argumentlist.pw
& 'C:\Program Files\Putty\Plink.exe' -ssh `"admin#*.*.*.*" -pw `$pw -batch whoami
"#)
$testParm = #{
pw = Read-Host "password"
}
$testjob = Start-Job -scriptblock $test_scriptblock -Argumentlist $testParm
$i = 0
do {
$i++
sleep 2
$results = Receive-Job $testjob
ForEach ($result in $results) {
Write-Host $result
}
if ($testjob.State -eq "Completed") {
$jobcompleted = $true
}
If ($i -gt 10) {
Stop-job $testjob
$jobcompleted = $true
}
} until ($jobcompleted)
Just add the stderr redirect to your plink or pscp commandline, to an extra dummy file, like
pscp ... 2> stderr.txt
With a caveat that it may swallow other valid error msgs, at your own risk :)
There's no way to suppress keyboard-interactive prompts in Plink.
I suggest you use a native .NET SSH implementation, like SSH.NET instead of driving an external console application.
Ir was a bit cumbersome, but finally I managed to suppress the "keyboard-interactive" messages this way:
[String] $Plink = 'C:\Program Files\PuTTY\plink.exe'
[Array] $PlinkPar = #("-ssh", "-l", $usr, "-pw", $pwd, $hst) # Set plink parameters
[Boolean] $PlinkOK = $True
Write-Host "Accept possibly unknown host key"
"y" | & $Plink $PlinkPar "exit" 2>&1 | Tee-Object -Variable PlinkOut | Out-Null
$PlinkOut | Foreach-Object {
$PlinkStr = $_.ToString()
If ($_ -is [System.Management.Automation.ErrorRecord]) {
If (! $PlinkStr.Contains("eyboard-interactive")) {
Write-Host "Error: $PlinkStr"
$PlinkOK = $False
}
} else {
Write-Host "$PlinkStr"
}
}
If (! $PlinkOK) { exit }
$PlinkPar += "-batch
And the output is like this:
>powershell .\InstCR.ps1 -usr myuser -pwd mypassword -hst doesnotexist
Accept possibly unknown host key
Error: Unable to open connection:
Error: Host does not exist
This plink call is just to accept a possibly unknown host key (without "-batch" and piping the "y" to answer the prompt). Then "-batch" is added to the Plink parameters to be used on all subsequent plink calls.
I am currently trying to import a .psm1 file dynamically into a script block to execute it.
I am using parallelisation along with jobs as I need to trigger several modules simultaneously as different users.
This is the code:
$tasksToRun | ForEach-Object -Parallel {
$ScriptBlock = {
param ($scriptName, $Logger, $GlobalConfig, $scriptsRootFolder )
Write-Output ("hello $($scriptsRootFolder)\tasks\$($scriptName)")
Import-Module ("$($scriptsRootFolder)\tasks\$($scriptName)")
& $scriptName -Logger $Logger -GlobalConfig $GlobalConfig
}
$job = Start-Job -scriptblock $ScriptBlock `
-credential $Cred -Name $_ `
-ArgumentList ($_, $using:Logger, $using:globalConfig, $using:scriptsRootFolder) `
Write-Host ("Running task $_")
$job | Wait-job -Timeout $using:timeout
if ($job.State -eq 'Running') {
# Job is still running, stop it
$job.StopJob()
Write-Host "Stopped $($job.Name) task as it took too long"
}
else {
# Job completed normally, get the results
$job | Receive-Job
Write-Host "Finished task $($job.Name)"
}
}
The logger variable is a hashtable as defined here:
$Logger = #{
generalLog = $function:Logger
certificateLog = $function:LoggerCertificate
alertLog = $function:LoggerAlert
endpointServiceLog = $function:LoggerEndpointService
}
Currently, it is erroring with the following:
ObjectNotFound: The term
' blah blah blah, this is the code straight from the logger function '
is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
The logger function servers the purpose of logging to a file in a specific way, it is generalised to that it can be used across many tasks.
A cut down example of a logger (probably won't compile, just deleted a bunch of lines to give you the general idea):
function LoggerEndpointService {
param (
# The full service name.
[string]$ServiceFullName,
# The unique identifier of the service assigned by the operating system.
[string]$ServiceId,
# The description of the service.
[string]$Description,
# The friendly service name.
[string]$ServiceFriendlyName,
# The start mode for the service. (disabled, manual, auto)
[string]$StartMode,
# The status of the service. (critical, started, stopped, warning)
[string]$Status,
# The user account associated with the service.
[string]$User,
# The vendor and product name of the Endpoint solution that reported the event, such as Carbon Black Cb Response.
[string]$VendorProduct
)
$ServiceFullName = If ([string]::IsNullOrEmpty($ServiceFullName)) { "" } Else { $ServiceFullName }
$ServiceId = If ([string]::IsNullOrEmpty($ServiceId)) { "" } Else { $ServiceId }
$ServiceFriendlyName = If ([string]::IsNullOrEmpty($ServiceFriendlyName)) { "" } Else { $ServServiceFriendlyNameiceName }
$StartMode = If ([string]::IsNullOrEmpty($StartMode)) { "" } Else { $StartMode }
$Status = If ([string]::IsNullOrEmpty($Status)) { "" } Else { $Status }
$User = If ([string]::IsNullOrEmpty($User)) { "" } Else { $User }
$Description = If ([string]::IsNullOrEmpty($Description)) { "" } Else { $Description }
$VendorProduct = If ([string]::IsNullOrEmpty($VendorProduct)) { "" } Else { $VendorProduct }
$EventTimeStamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK"
$Delay = 100
For ($i = 0; $i -lt 30; $i++) {
try {
$logLine = "{{timestamp=""{0}"" dest=""{1}"" description=""{2}"" service=""{3}"" service_id=""{4}""" `
+ "service_name=""{5}"" start_mode=""{6}"" vendor_product=""{7}"" user=""{8}"" status=""{9}""}}"
$logLine -f $EventTimeStamp, $env:ComputerName, $Description, $ServiceFullName, $ServiceId, $ServiceFriendlyName, $StartMode, $VendorProduct, $User, $Status | Add-Content $LogFile -ErrorAction Stop
break;
}
catch {
Start-Sleep -Milliseconds $Delay
}
if ($i -eq 29) {
Write-Error "Alert logger failed to log, likely due to Splunk holding the file, check eventlog for details." -ErrorAction Continue
if ([System.Diagnostics.EventLog]::SourceExists("SDOLiveScripts") -eq $False) {
Write-Host "Doesn't exist"
New-EventLog -LogName Application -Source "SDOLiveScripts"
}
Write-EventLog -LogName "Application" -Source "SDOLiveScripts" `
-EventID 1337 `
-EntryType Error `
-Message "Failed to log to file $_.Exception.InnerException.Message" `
-ErrorAction Continue
}
}
}
Export-ModuleMember -Function LoggerEndpointService
If anyone could help that'd be great, thank you!
As mentioned in the comments, PowerShell Jobs execute in separate processes and you can't share live objects across process boundaries.
By the time the job executes, $Logger.generalLog is no longer a reference to the scriptblock registered as the Logger function in the calling process - it's just a string, containing the definition of the source function.
You can re-create it from the source code:
$actualLogger = [scriptblock]::Create($Logger.generalLog)
or, in your case, to recreate all of them:
#($Logger.Keys) |ForEach-Object { $Logger[$_] = [scriptblock]::Create($Logger[$_]) }
This will only work if the logging functions are completely independent of their environment - any references to variables in the calling scope or belonging to the source module will fail to resolve!
I have written a script to update the users' contact information in Azure AD. The CSV I'm using is an export from our local AD. I found some examples as a starting place and this is what I have hacked out ...
Start-Transcript "transcript.log"
# Connect to AzureAD
Connect-AzureAD
# Get CSV content
$CSVrecords = Import-Csv userexport.csv -Delimiter ","
# Create arrays for skipped and failed users
$SkippedUsers = #()
$FailedUsers = #()
# Loop trough CSV records
foreach ($CSVrecord in $CSVrecords) {
$upn = $CSVrecord.samaccountname + "#daytonrogers.com"
$user = Get-AzureADUser -Filter "userPrincipalName eq '$upn'"
if ($user) {
$command = "Set-AzureADUser -ObjectID $($user.objectid) "
if ($CSVrecord.title) {$command = "$command -jobtitle '$($CSVrecord.title)'"}
if ($CSVrecord.department) {$command = "$command -department '$($CSVrecord.department)'"}
if ($CSVrecord.office) {$command = "$command -PhysicalDeliveryOfficeName '$($CSVrecord.office)'"}
if ($CSVrecord.officephone) {$command = "$command -TelephoneNumber '$($CSVrecord.officephone)'"}
if ($CSVrecord.fax) {$command = "$command -FacsimileTelephoneNumber '$($CSVrecord.fax)'"}
if ($CSVrecord.mobilephone) {$command = "$command -Mobile '$($CSVrecord.mobilephone)'"}
if ($CSVrecord.streetaddress) {$command = "$command -streetaddress '$($CSVrecord.streetaddress)'"}
if ($CSVrecord.city) {$command = "$command -city '$($CSVrecord.city)'"}
if ($CSVrecord.state) {$command = "$command -state '$($CSVrecord.state)'"}
if ($CSVrecord.postalcode) {$command = "$command -postalcode '$($CSVrecord.postalcode)'"}
Write-Information $command
try{
$command
} catch {
$FailedUsers += $upn
Write-Warning "$upn user found, but FAILED to update."
}
}
else {
Write-Warning "$upn not found, skipped"
$SkippedUsers += $upn
}
}
Stop-Transcript
It runs and builds the command just fine. However, none of the users get updated. If I copy / past the command from the transcript.log file, it works. It just does not work if I run the script from the PowerShell command line.
What am I missing here?
Creating command strings and then executing them is going to lead to bad practices I think. You will have to resort to Invoke-Expression. That is something we want to avoid. I would build a hash table with your parameters and use splatting. From that, you can build your command string to send to the information stream. See below for an example.
if ($user) {
$command = "Set-AzureADUser"
$params = #{}
$params.ObjectID = $user.objectid
if ($CSVrecord.title) {$params.jobtitle = $CSVrecord.title}
if ($CSVrecord.department) {$params.department = $CSVrecord.department}
if ($CSVrecord.office) {$params.PhysicalDeliveryOfficeName = $CSVrecord.office}
if ($CSVrecord.officephone) {$params.TelephoneNumber = $CSVrecord.officephone}
if ($CSVrecord.fax) {$params.FacsimileTelephoneNumber = $CSVrecord.fax}
if ($CSVrecord.mobilephone) {$params.Mobile = $CSVrecord.mobilephone}
if ($CSVrecord.streetaddress) {$params.streetaddress = $CSVrecord.streetaddress}
if ($CSVrecord.city) {$params.city = $CSVrecord.city}
if ($CSVrecord.state) {$params.state = $CSVrecord.state}
if ($CSVrecord.postalcode) {$params.postalcode = $CSVrecord.postalcode}
Write-Information "$command $($params.GetEnumerator() |% {"-{0} '{1}'" -f $_.Key,$_.Value})"
& $command #params
}
Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?
For example:
$global:ERRORS = #();
$global:PROGERR = #();
function test() {
# Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
$OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
if ( $OUTPUT | select-string -Pattern "foo" ) {
# do stuff
} else {
$global:ERRORS += "test(): oh noes! 'foo' missing!";
}
}
test;
if ( #($global:ERRORS).length -gt 0 ) {
Write-Host "Script specific error occurred";
foreach ( $err in $global:ERRORS ) {
$host.ui.WriteErrorLine("err: $err");
}
} else {
Write-Host "Script ran fine!";
}
if ( #($global:PROGERR).length -gt 0 ) {
# do stuff
} else {
Write-Host "External program ran fine!";
}
A dull example however I am wondering if that is possible?
One option is to combine the output of stdout and stderr into a single stream, then filter.
Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
The easiest way to do this is to use a file for the stderr output, e.g.:
$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
I would also use $LastExitCode to check for errors from native console EXE files.
You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:
$command = "myexecutable.exe my command line params"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
It is just another possibility to supplement what was already given.
Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:
PowerShell -File ".\FileName.ps1"
An alternative that seems to work under most circumstances is this:
$stdOutAndError = Invoke-Expression "$command 2>&1"
Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.
In case you want to get any from a PowerShell script and to pass a function name followed by any arguments you can use dot sourcing to call the function name and its parameters.
Then using part of James answer to get the $output or the $errors.
The .ps1 file is called W:\Path With Spaces\Get-Something.ps1 with a function inside named Get-It and a parameter FilePath.
Both the paths are wrapped in quotes to prevent spaces in the paths breaking the command.
$command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
# This will get its output.
$output
# This will output the errors.
$errors
Copied from my answer on how to capture both output and verbose information in different variables.
Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.
In this way, it will not only take longer time, but also increase the probability of error.
In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).
# First, declare a method that outputs both streams at the same time.
function thisFunc {
[cmdletbinding()]
param()
Write-Output 'Output'
Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
Then we verify the contents of these two variables
$VerboseStream.getType().FullName
$String.getType().FullName
The following information should appear on the console:
PS> System.Management.Automation.VerboseRecord
System.String
'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.
Separately, preserving formatting
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta2=""
foreach ($el in $r.Meta2){
$Meta2+=$el
}
$Meta2=($Meta2 -split "[`r`n]") -join "`n"
$Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
[Console]::Write("stderr:`n");
[Console]::Write($Meta2);
[Console]::Write("`n");
$Meta3=""
foreach ($el in $r.Meta3){
$Meta3+=$el
}
$Meta3=($Meta3 -split "[`r`n]") -join "`n"
$Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
[Console]::Write("stdout:`n");
[Console]::Write($Meta3);
[Console]::Write("`n");
I am working on an error handling method for my PowerShell scripts. I pass it the error via try/catch on the catch, but I want to iterate through the original params from the command line that called it in order to create an error log and error email.
Here's what I have so far:
# --params--
param(
[string]$Directory,
[string]$ArchiveDirectory,
[string]$ErrorDirectory,
[string]$ErrorEmailFrom,
[string]$ErrorEmailTo,
[string]$ErrorEmailSubject,
[string]$ErrorSMTP,
[string]$FTPSite,
[string]$FTPUser,
[string]$FTPPass,
[string]$FTPRemoteDir
)
# list of arguments for debug
$paramList = $args
# --functions--
function Handle-MyError
{
Write-Host "handle-error"
Write-Host $args[0]; # this is the exception passed in
# -Email alert-
$subject = $ErrorEmailSubject + $FTPSite
# build message
$message = Get-Date -Format "yyyy-mm-dd hh:mm:ss"
$message += "`r`nError: " + $FTPSite + " : " + $args[0]
$message += "`r`nParameters:`r`n"
# Grab each parameter value, using Get-Variable
for ($i=0;$i -lt $paramList.Length; $i++)
{
$message += $paramList[$i]
}
# send email
$smtp = New-Object Net.Mail.SmtpClient($ErrorSMTP)
$smtp.Send($ErrorEmailFrom, $ErrorEmailTo, $subject, $message)
# drop error file
$theDate = Get-Date -Format "yyyymmdd"
$errorFile = $ErrorDirectory + "\" + $theDate + "_ERROR.txt"
Write-Host $errorFile
$message | Out-File $errorFile -Append
}
and in my try/catch:
catch [Exception]
{
Write-Host "SPOT 1"
Handle-MyError $_.
}
At the top, I try to save the original $args as $paramList to loop through later, but it's not working. Inside the Handle-MyError method, $args becomes the error that is passed so I thought if I save the original $argsas $paramList I could access it later, but it's wonky... Ideas?
There are several ways, in order of worst to best:
Use Get-Variable with Scope parameter. Scope number can differ, but it should be at least 2 (Script->Catch->Handle-MyError)
function Handle-MyError
{
Write-Host (Get-Variable -Name ErrorEmailFrom -ValueOnly -Scope 2)
}
Using $Script: prefix
function Handle-MyError
{
Write-Host $Script:ErrorEmailFrom
}
Using $PSBoundParameters
# list of arguments for debug
$paramList = $PsBoundParameters
function Handle-MyError
{
Param
(
$Exception,
$Cfg
)
Write-Host $Cfg.ErrorEmailFrom
}
catch [Exception]
{
Write-host "SPOT 1"
Handle-MyError -Exception $_ -Cfg $paramList
}
Using splatting:
$paramList = $PsBoundParameters
function Handle-MyError
{
Param
(
$Exception,
$ErrorDirectory,
$ErrorEmailFrom,
$ErrorEmailTo,
$ErrorEmailSubject,
$ErrorSMTP
)
Write-Host $ErrorEmailFrom
}
catch [Exception]
{
Write-host "SPOT 1"
Handle-MyError #paramList -Exception $_
}
Here's my final code after some help from #beatcracker.
I combined two pieces of the puzzle.
I need to save the initial params in a local var and Two, ($paramList = $PsBoundParameters)
Access this var/list using .GetEnumerator()
# --params--
param(
[string]$Directory,
[string]$ArchiveDirectory,
[string]$ErrorDirectory,
[string]$ErrorEmailFrom,
[string]$ErrorEmailTo,
[string]$ErrorEmailSubject,
[string]$ErrorSMTP,
[string]$FTPSite,
[string]$FTPUser,
[string]$FTPPass,
[string]$FTPRemoteDir
)
# set params as var for debug later
$paramList = $PsBoundParameters
# --functions--
function Handle-MyError
{
Write-Host "handle-error"
#write-host "Exception:" $args[0]; # this is the exception passed in
# -Email alert-
# build subject
$subject = $ErrorEmailSubject + " " + $FTPSite
# build message
$message = Get-Date -format s
$message += "`r`nError Message: " + $args[0]
$message += "`r`nParameters:`r`n"
$paramList.GetEnumerator() | ForEach-Object `
{
#Write-Host $_.Key "=" $_.Value
if ($_.Key -ne "FTPPass"){
$message += "`r`n" + $_
}
}
}