Trying to understand a PowerShell function - powershell

I'm brand new to PowerShell and am working on modifying a script to combine 4 functions into one. I am having a little trouble understanding how all the pieces of the individual functions fit together. For example, it has a $msg variable that doesn't seem to be declared anywhere else in the script. So essentially i'm looking for any advice on how to make these fit.
##LogSuccess function
##Display provided message as a SUCCESS in green, with SUCCESS: prefix
##If logging mode is set to true, also write SUCCESS message to $logfileSS
Function global:LogSuccess($msg){
Write-Host "SUCCESS: " $msg -ForegroundColor "Green"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($global:loggingmode){
Write-Output "SUCCESS: " $msg | Out-File -filepath $global:logfile -Append
}
}
##LogError function
##Display provided message as an error in red, with ERROR: prefix
##If logging mode is set to true, also write ERROR message to $logfile
Function global:LogError($msg){
Write-Host "ERROR: " $msg -ForegroundColor "Red"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($global:loggingmode){
Write-Output "ERROR: " $msg | Out-File -filepath $global:logfile -Append
}
}
##LogWarning function
##Display provided message as a WARNING in yellow, with WARNING: prefix
##If logging mode is set to true, also write WARNING message to $logfile
Function global:LogWarning($msg){
Write-Host "WARNING: " $msg -ForegroundColor "Yellow"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($global:loggingmode){
Write-Output "WARNING: " $msg | Out-File -filepath $global:logfile -Append
}
}
##Logging function
##Display provided message as a general information message in cyan
##If logging mode is set to true, also write information message to $logfile
Function global:Logging($msg){
Write-Host $msg -ForegroundColor "Cyan"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($global:loggingmode){
Write-Output $msg | Out-File -filepath $global:logfile -Append
}
}

from my point of view those functions are not designed as intended, e.g.:
Function global:Logging($msg){
Write-Host $msg -ForegroundColor "Cyan"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($global:loggingmode){
Write-Output $msg | Out-File -filepath $global:logfile -Append
}
}
PowersShell functions accept named input parameters and are outputting objects in general. In simple words this is the concept. Currently those functions do not return objects they do update/use variables with the scope global. This is a dangerous approach and not needed.
About scopes: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes?view=powershell-7.2
About functions: https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/09-functions?view=powershell-7.2
Back to the one example you should do something like this:
Function write-LogFile {
<#
.Description
Enter your description here
.Parameter Message
Enter your description of the parameter here
.Parameter LogToTextFile
Enter the desscription of the parameter here
.Parameter Path
Enter the desscription of the parameter here
.Example
Enter example calls here
#>
param (
[parameter(Mandatory=$true,Position=1)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[parameter(Mandatory=$true,Position=2)]
[switch]$LogToTextFile,
[parameter(Mandatory=$true,Position=3)]
[ValidateNotNullOrEmpty()]
[string]$Path
)
Begin {
}
Process {
try {
Write-Host $msg -ForegroundColor "Cyan"
$timestamp = Get-Date
$msg = $timestamp.ToString() + ": " + $msg
if ($LogToTextFile){
Write-Output $msg | Out-File -filepath $path -Append
}
Else {
$msg
}
}
Catch {
write-error $_
}
}
End{
}
}
So I think before you start to merge those function you need first to understand the concept how to write a function. The provided links should help you to find the right path...

Related

List SPN's Script - Write results to file issue

Good morning everyone.
I found this script on the InterWeb's which works phenominal. HOWEVER... No matter what I try, and where I put it, I can't seem to get it to do the results to an out-file.
What the hell am I doing wrong, and where does the variable need to go?
# Source / credit:
# https://social.technet.microsoft.com/wiki/contents/articles/18996.active-directory-powershell-script-to-list-all-spns-used.aspx
cls
$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$search.filter = "(servicePrincipalName=*)"
## You can use this to filter for OU's:
## $results = $search.Findall() | ?{ $_.path -like '*OU=whatever,DC=whatever,DC=whatever*' }
$results = $search.Findall()
foreach( $result in $results ) {
$userEntry = $result.GetDirectoryEntry()
Write-host "Object Name = " $userEntry.name -backgroundcolor "yellow" -foregroundcolor "black"
Write-host "DN = " $userEntry.distinguishedName
Write-host "Object Cat. = " $userEntry.objectCategory
Write-host "servicePrincipalNames"
$i=1
foreach( $SPN in $userEntry.servicePrincipalName ) {
Write-host "SPN ${i} =$SPN"
$i+=1
}
Write-host ""
}

Check multiple file paths exists before running script

I need to check that I have all files before running the rest of my powershell script.
What is the best way to go through the below code and after it has found each file required, continue with the rest of my script. Or if any of the files are not found, I send an email alert along with exiting the script.
# collect log one
if (Test-Path $logone) {
$one = Import-Csv -Path $logone
Write-Host "Log one found"
}
else {
#Send email "Log one not found!"
Write-Host "Log one not found!"
}
# collect log two
if (Test-Path -Path $logtwo) {
$two = Import-Csv -Path $logtwo
Write-Host "Log two found"
}
else {
#Send email "Log two not found!"
Write-Host "Log two not found!"
}
# collect log three data
if (Test-Path -Path $logthree) {
$three = Import-Csv -Path $logthree
Write-Host "Log three found"
}
else {
#Send email "Log three not found!"
Write-Host "Log three not found!"
}
Would I just add the following code below what I have:
if (Test-Path $logone) -and (Test-Path $logtwo) -and (Test-Path $logthree) {
# continue with the rest of my code
}
else {
Write-Host "Script exited with error"
Exit
}
Is there a cleaner way of doing this?
Based on I.T Delinquent's answer here an example of how to achieve this:
$logOne = "C:\Temp\log1"
$logTwo = "C:\Temp\log2"
$logThree = "C:\Temp\log3"
$mandatoryLogs = $logOne,$logTwo,$logThree
$errors = #()
$count = 0
$logs = #{}
foreach($log in $mandatoryLogs){
$count++
if(Test-Path $log){
$logs["log$count"] = Import-Csv -Path $log
Write-Host "Log $count found!"
}else{
$errors += "$log is missing"
}
}
if($errors){
$body = #"
Dear Admin,
The following errors occured: $($errors | Out-String)
Regards
"#
try{
Send-MailMessage -Body $body # -To -From etc etc
}catch{
throw $_
}
throw "Quiting because of errors, mail has been sent"
}else{
Write-Host "Continuing script" -ForegroundColor Green
}
Next you can access each of the logs in the following way:
$logs['log1']
Or:
Write-Host "$($logs['log3'].randomPropertyWhichExistsInTheCSV)"
Hope this helps!

PowerShell switch between multiple prompt functions and scoping

I have found the following behaviour that I do not understand. I have some functions in my $profile (specifically, that change my prompt, so function prmopt { }) with settings that change my prompt and when I start a console, if I dotsource the function ( . PromptCustom ), it takes full effect and the new prompt takes over. However, I don't want my $profile to be too big so I moved my five or so different prompts into a Module, but when I try to dotsource any of them, nothing happens. They just output what the prompt might look like but do not take over as the default prompt.
The objective is to be able to have multiple functions that switch between prompts as required (i.e. not a single prompt that applies to every console, for which I would just put function prompt in my $profile). When I move functions that follow the below template to a Module, they all break and so I was wondering if that was a scoping issue, and how to achieve the goal of having mutltiple prompt functions in a Module that I can switch between instead of being forced to keep them in my $profile? (Edit: updating this question as #mklement0 pointed out, since really it's about the required objective i.e. having prompts that I can switch between).
Here is one of my prompt functions that dotsources and takes over as the default prompt perfectly if this function is defined in my $profile but does nothing if it is put into a Module:
function PromptShortenPath {
# https://stackoverflow.com/questions/1338453/custom-powershell-prompts
function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
function prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
if ($MyInvocation.InvocationName -eq "PromptShortenPath") {
"`nWarning: Must dotsource '$($MyInvocation.MyCommand)' or it will not be applied to this session.`n`n . $($MyInvocation.MyCommand)`n"
} else {
. prompt
}
}
Scepticalist's helpful answer provides an effective solution for activating your prompt function at the time of import.
The approach in your question for activating the function on demand, by later dot-sourcing a function in which the prompt function is nested, fundamentally cannot work as written if that function is imported from a module, as explained next; for a solution, see the bottom section.
As for what you tried:
. prompt
This doesn't dot-source the definition of function prompt, it runs the function in the sourcing scope.
In effect, it (pointlessly) prints (once, as output) what the prompt string should be and makes the function-local variables linger in the caller's scope.
Therefore, by nesting the prompt function definition inside the PromptShortenPath function, dot-sourcing that defines the prompt function in the caller's scope automatically, along with the shorten-path function[1]
If your PromptShortenPath function is defined outside of a module, dot-sourcing it means that the sourcing scope is the (non-module) caller's current scope, which defines the nested functions there, and with the appearance of a new prompt function, the interactive prompt string changes, as intended.
By contrast, if your PromptShortenPath function is defined inside a module, dot-sourcing it means that the sourcing scope is the module of origin, which means that the caller's current scope is unaffected, and never sees the nested shorten-path and prompt functions - thus, the interactive prompt string does not change.
This bears repeating: dot-sourcing a function (as opposed to a script) runs the function in the current scope of the scope domain of origin rather than the caller's current scope; that is, dot-sourcing a function from a module invariably runs it in that module's current scope, which is distinct from and unrelated to the caller's scope (unless the caller happens to be the top-level scope inside the same module).
By contrast, Scepticalist's solution, by making shorten-path and prompt functions top-level functions of the module, implicitly (exports and) imports them both into the caller's scope with Import-Module, and, again, the appearance of a new prompt function in the caller's scope changes the interactive prompt string, albeit at the time of importing.
Alternative approach that also works with modules:
The simplest solution is to define the nested function with scope specifier global:, which directly defines it in the global scope, irrespective of what scope contains the definition.
As a beneficial side effect, you then no longer have to dot-source the prompt-activating function on invocation.
Note that the solution below embeds helper function shorten-path in the global:prompt function to ensure its availability to the latter; the alternative would be to define shorten-path as global:shorten-path too, but there is no need to clutter the global scope with helper functions, especially given that name collisions can occur.
# Use a dynamic module to simulate importing the `Set-Prompt` function
# from a (regular, persisted) module.
$null = New-Module {
function Set-Prompt {
# Note the `global:` prefix.
Function global:prompt {
# Note the *embedded* definition of helper function shorten-path,
# which makes it available to the enclosing function only and avoids
# the need to make the helper function global too.
Function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)', '\$1$2')
}
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
Write-Host "$([char]0x0A7) " -n -f $cloc
Write-Host ([net.dns]::GetHostName()) -n -f $chost
Write-Host ' {' -n -f $cdelim
Write-Host (shorten-path (pwd).Path) -n -f $cloc
Write-Host '}' -n -f $cdelim
return ' '
}
}
}
# Now that Set-Prompt is imported, invoke it as you would
# any function, and the embedded `prompt` function will take effect.
Set-Prompt
[1] Note that while shorten-path follows PowerShell's noun-verb naming convention in principle, shorten is not on the list of approved verbs.
If you remove the outer function and save as modulename.psm1 in a folder by the same name within a module path:
Function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
Function prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
Now just:
Import-Module modulename
Note that the new prompt now takes effect upon importing the function
I've finally come down to the following solution. Thanks for helping with this #mklement / #Scepticalist. In the end, I really only needed the global: invocation. I didn't want a dynamic function (though interesting to see that, will probably be useful) and I did not want the prompt to activate upon importing the module (this was explicitly what I wanted to avoid in fact!).
All of these now work by dropping into any personal Module. Importing the Module will not activate the prompt (this was my desired outcome). Each prompt can then be activated on demand simply by invoking the function that sets that prompt (or its alias).
Edit: Please feel free to add any more prompt functions that do interesting things. I'm always very interested in seeing more useful tricks and variations for prompt configurations! :)
function PromptDefault {
# get-help about_Prompt
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_prompts?view=powershell-7
function global:prompt {
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml
$Elevated = ""
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
if ((New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {$Elevated = "Administrator: "}
# $TitleVer = "PS v$($PSVersionTable.PSversion.major).$($PSVersionTable.PSversion.minor)"
$TitleVer = "PowerShell"
$Host.UI.RawUI.WindowTitle = "$($Elevated)$($TitleVer)"
}
}
# More simple alternative prompt, need to dotsource this
function PromptTimeUptime {
function global:prompt {
# Adds date/time to prompt and uptime to title bar
$Elevated = "" ; if (Test-Admin) {$Elevated = "Administrator: "}
$up = Uptime
$Host.UI.RawUI.WindowTitle = $Elevated + "PowerShell [Uptime: $up]" # Title bar info
$path = Get-Location
Write-Host '[' -NoNewline
Write-Host (Get-Date -UFormat '%T') -ForegroundColor Green -NoNewline # $TitleDate = Get-Date -format "dd/MM/yyyy HH:mm:ss"
Write-Host '] ' -NoNewline
Write-Host "$path" -NoNewline
return "> " # Must have a line like this at end of prompt or you always get " PS>" on the prompt
}
}
function PromptTruncatedPaths {
# https://www.johndcook.com/blog/2008/05/12/customizing-the-powershell-command-prompt/
function global:prompt {
$cwd = (get-location).Path
[array]$cwdt=$()
$cwdi = -1
do {$cwdi = $cwd.indexofany("\", $cwdi+1) ; [array]$cwdt+=$cwdi} until($cwdi -eq -1)
if ($cwdt.count -gt 3) { $cwd = $cwd.substring(0,$cwdt[0]) + ".." + $cwd.substring($cwdt[$cwdt.count-3]) }
$host.UI.RawUI.WindowTitle = "$(hostname) – $env:USERDNSDOMAIN$($env:username)"
$host.UI.Write("Yellow", $host.UI.RawUI.BackGroundColor, "[PS]")
" $cwd> "
}
}
function PromptShortenPath {
# https://stackoverflow.com/questions/1338453/custom-powershell-prompts
function global:shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
function global:prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
}
function PromptUserAndExecutionTimer {
function global:prompt {
### Title bar info
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
$Elevated = ""
if ((New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {$Elevated = "Admin: "}
$TitleVer = "PS v$($PSVersionTable.PSversion.major).$($PSVersionTable.PSversion.minor)"
# $($executionContext.SessionState.Path.CurrentLocation.path)
### Custom Uptime without seconds (not really necessary)
# $wmi = gwmi -class Win32_OperatingSystem -computer "."
# $LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
# [TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
# $s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
# $TitleUp = "[Up: $($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min]"
$Host.UI.RawUI.WindowTitle = "$($Elevated) $($TitleVer)" # $($TitleUp)"
### History ID
$HistoryId = $MyInvocation.HistoryId
# Uncomment below for leading zeros
# $HistoryId = '{0:d4}' -f $MyInvocation.HistoryId
Write-Host -Object "$HistoryId " -NoNewline -ForegroundColor Cyan
### Time calculation
$Success = $?
$LastExecutionTimeSpan = if (#(Get-History).Count -gt 0) {
Get-History | Select-Object -Last 1 | ForEach-Object {
New-TimeSpan -Start $_.StartExecutionTime -End $_.EndExecutionTime
}
}
else {
New-TimeSpan
}
$LastExecutionShortTime = if ($LastExecutionTimeSpan.Days -gt 0) {
"$($LastExecutionTimeSpan.Days + [Math]::Round($LastExecutionTimeSpan.Hours / 24, 2)) d"
}
elseif ($LastExecutionTimeSpan.Hours -gt 0) {
"$($LastExecutionTimeSpan.Hours + [Math]::Round($LastExecutionTimeSpan.Minutes / 60, 2)) h"
}
elseif ($LastExecutionTimeSpan.Minutes -gt 0) {
"$($LastExecutionTimeSpan.Minutes + [Math]::Round($LastExecutionTimeSpan.Seconds / 60, 2)) m"
}
elseif ($LastExecutionTimeSpan.Seconds -gt 0) {
"$($LastExecutionTimeSpan.Seconds + [Math]::Round($LastExecutionTimeSpan.Milliseconds / 1000, 1)) s"
}
elseif ($LastExecutionTimeSpan.Milliseconds -gt 0) {
"$([Math]::Round($LastExecutionTimeSpan.TotalMilliseconds, 0)) ms"
# ms are 1/1000 of a sec so no point in extra decimal places here
}
else {
"0 s"
}
if ($Success) {
Write-Host -Object "[$LastExecutionShortTime] " -NoNewline -ForegroundColor Green
}
else {
Write-Host -Object "! [$LastExecutionShortTime] " -NoNewline -ForegroundColor Red
}
### User, removed
$IsAdmin = (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
# Write-Host -Object "$($env:USERNAME)$(if ($IsAdmin){ '[A]' } else { '[U]' }) " -NoNewline -ForegroundColor DarkGreen
# Write-Host -Object "$($env:USERNAME)" -NoNewline -ForegroundColor DarkGreen
# Write-Host -Object " [" -NoNewline
# if ($IsAdmin) { Write-Host -Object 'A' -NoNewline -F Red } else { Write-Host -Object 'U' -NoNewline }
# Write-Host -Object "] " -NoNewline
Write-Host "$($env:USERNAME)" -NoNewline -ForegroundColor DarkGreen
Write-Host "[" -NoNewline
if ($IsAdmin) { Write-Host 'A' -NoNewline -F Red } else { Write-Host -Object 'U' -NoNewline }
Write-Host "] " -NoNewline
# ### Path
# $Drive = $pwd.Drive.Name
# $Pwds = $pwd -split "\\" | Where-Object { -Not [String]::IsNullOrEmpty($_) }
# $PwdPath = if ($Pwds.Count -gt 3) {
# $ParentFolder = Split-Path -Path (Split-Path -Path $pwd -Parent) -Leaf
# $CurrentFolder = Split-Path -Path $pwd -Leaf
# "..\$ParentFolder\$CurrentFolder"
# go # }
# elseif ($Pwds.Count -eq 3) {
# $ParentFolder = Split-Path -Path (Split-Path -Path $pwd -Parent) -Leaf
# $CurrentFolder = Split-Path -Path $pwd -Leaf
# "$ParentFolder\$CurrentFolder"
# }
# elseif ($Pwds.Count -eq 2) {
# Split-Path -Path $pwd -Leaf
# }
# else { "" }
# Write-Host -Object "$Drive`:\$PwdPath" -NoNewline
Write-Host $pwd -NoNewline
return "> "
}
}
function PromptSlightlyBroken {
# https://community.spiceworks.com/topic/1965997-custom-cmd-powershell-prompt
# if ($MyInvocation.InvocationName -eq "PromptOverTheTop") {
# "`nWarning: Must dotsource '$($MyInvocation.MyCommand)' or it will not be applied to this session.`n`n . $($MyInvocation.MyCommand)`n"
# } else {
if ($host.name -eq 'ConsoleHost') {
# fff
$Shell = $Host.UI.RawUI
$Shell.BackgroundColor = "Black"
$Shell.ForegroundColor = "White"
$Shell.CursorSize = 10
}
# $Shell=$Host.UI.RawUI
# $size=$Shell.BufferSize
# $size.width=120
# $size.height=3000
# $Shell.BufferSize=$size
# $size=$Shell.WindowSize
# $size.width=120
# $size.height=30
# $Shell.WindowSize=$size
# $Shell.BackgroundColor="Black"
# $Shell.ForegroundColor="White"
# $Shell.CursorSize=10
# $Shell.WindowTitle="Console PowerShell"
function global:Get-Uptime {
$os = Get-WmiObject win32_operatingsystem
$uptime = (Get-Date) - ($os.ConvertToDateTime($os.lastbootuptime))
$days = $Uptime.Days ; if ($days -eq "1") { $days = "$days day" } else { $days = "$days days"}
$hours = $Uptime.Hours ; if ($hours -eq "1") { $hours = "$hours hr" } else { $hours = "$hours hrs"}
$minutes = $Uptime.Minutes ; if ($minutes -eq "1") { $minutes = "$minutes min" } else { $minutes = "$minutes mins"}
$Display = "$days, $hours, $minutes"
Write-Output $Display
}
function Spaces ($numspaces) { for ($i = 0; $i -lt $numspaces; $i++) { Write-Host " " -NoNewline } }
# $MaximumHistoryCount=1024
$IPAddress = #(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].IPAddress[0]
$IPGateway = #(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].DefaultIPGateway[0]
$UserDetails = "$env:UserDomain\$env:UserName (PS-HOME: $HOME)"
$PSExecPolicy = Get-ExecutionPolicy
$PSVersion = "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) ($PSExecPolicy)"
$ComputerAndLogon = "$($env:COMPUTERNAME)"
$ComputerAndLogonSpaces = 28 - $ComputerAndLogon.Length
Clear
Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
Write-Host "| ComputerName: " -nonewline -ForegroundColor Green; Write-Host $ComputerAndLogon -nonewline -ForegroundColor White ; Spaces $ComputerAndLogonSpaces ; Write-Host "UserName:" -nonewline -ForegroundColor Green ; Write-Host " $UserDetails" -ForegroundColor White
Write-Host "| Logon Server: " -nonewline -ForegroundColor Green; Write-Host $($env:LOGONSERVER)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "IP Address:`t" -nonewline -ForegroundColor Green ; Write-Host "`t$IPAddress ($IPGateway)" -ForegroundColor White
Write-Host "| Uptime: " -nonewline -ForegroundColor Green; Write-Host "$(Get-Uptime)`t" -nonewline -ForegroundColor White; Write-Host "PS Version:`t" -nonewline -ForegroundColor Green ; Write-Host "`t$PSVersion" -ForegroundColor White
Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
# Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
# Write-Host "|`tComputerName:`t" -nonewline -ForegroundColor Green; Write-Host $($env:COMPUTERNAME)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "UserName:`t$UserDetails" -ForegroundColor White
# Write-Host "|`tLogon Server:`t" -nonewline -ForegroundColor Green; Write-Host $($env:LOGONSERVER)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "IP Address:`t$IPAddress ($IPGateway)" -ForegroundColor White
# Write-Host "|`tUptime:`t`t" -nonewline -ForegroundColor Green; Write-Host "$(Get-Uptime)`t" -nonewline -ForegroundColor White; Write-Host "PS Version:`t$PSVersion" -ForegroundColor White
# Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
function global:admin {
$Elevated = ""
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() )
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -eq $true) { $Elevated = "Administrator: " }
$Host.UI.RawUI.WindowTitle = "$Elevated$TitleVer"
}
admin
Set-Location C:\
function global:prompt{
$br = "`n"
Write-Host "[" -noNewLine
Write-Host $(Get-date) -ForegroundColor Green -noNewLine
Write-Host "] " -noNewLine
Write-Host "[" -noNewLine
Write-Host "$env:username" -Foregroundcolor Red -noNewLine
Write-Host "] " -noNewLine
Write-Host "[" -noNewLine
Write-Host $($(Get-Location).Path.replace($home,"~")) -ForegroundColor Yellow -noNewLine
Write-Host $(if ($nestedpromptlevel -ge 1) { '>>' }) -noNewLine
Write-Host "] "
return "> "
}
}
Set-Alias p0 PromptDefault
Set-Alias p-default PromptDefault
Set-Alias p-timer PromptUserAndExecutionTimer # Using this as my console default
Set-Alias p-short PromptShortenPath
Set-Alias p-trunc PromptTruncatedPaths
Set-Alias p-uptime PromptTimeUptime
Set-Alias p-broken PromptSlightlyBroken
# View current prompt with: (get-item function:prompt).scriptblock or cat function:\prompt

How can i correctly output my try-catch into a txt file?

I'm working on a powershell script that would allow me to see if my DHCP servers are running well and what's their current state (how much address left, how much of them are used...).
So far, so good. I'm getting all the info i need but, the first test here is to check if the server is responding pretty much.
For this, i'm using try-catch and i'd like to output into a .txt all of the servers that are not responding.
Problem : i'm only outputting the last server that did not respond, i'm not getting the previous one that did not respond aswell.
Tried | out-file
| set-content and | add-content
There's nothing that i've found searching seems to work.
$DHCPSRV=""
$myError=0
$myArray=#( Import-Csv .\CSV\DHCP_list.csv)
foreach ($element in $myArray) {
try {
Write-Output ""
$DHCPSRV=$element.FQDN
$Message = "Server DHCP: " + $DHCPSRV
Write-Output $Message
Write-Output ""
$srv=get-dhcpserverv4statistics -ComputerName $DHCPSRV
$Message ="Server start time : " + $srv.ServerStartTime
Write-Output $Message
$Message ="Number of address : " + $srv.TotalAddresses
Write-Output $Message
$Message ="Address used : " + $srv.AddressesInUse
Write-Output $Message
$Message ="% remaining : " + $srv.PercentageAvailable + " %"
Write-Output $Message
Get-DhcpServerSetting -ComputerName $DHCPSRV
Write-Output ""
}
catch{
Write-host "Server not responding " $DHCPSRV -BackgroundColor red -ForegroundColor White
$myerror=$error+1
$test = $DHCPSRV
}
}
if ($myError -eq 0){
Write-Output ""
Write-host "All DHCP are working good" -BackgroundColor green -ForegroundColor black
}
$test | Set-Content '.\Output\dhcp_failed.txt'
$test | Add-Content '.\Output\dhcp_failed.txt'
Write-Output ""
Write-Output ""
Write-Output "------------------------------------------------"
pause
I'd like to output all of the server that failed the try-catch test in my txt!
solved by bluuf - Thank you !
Just had to add the -append in my catch
catch{
Write-host "Server not responding " $DHCPSRV -BackgroundColor red -ForegroundColor White
$myerror=$error+1
$DHCPSRV | out-file '.\path\file.txt' -append
}
also added clear-content '.\path\file.txt' at the beginning of the script so my file get cleared every time i launch it!

access $args/params from inside method

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" + $_
}
}
}