PowerShell switch between multiple prompt functions and scoping - powershell
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
Related
Trying to understand a PowerShell function
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...
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 "" }
Enviroment Paths without overwriting String
I would like to ask question about how I should proceed or how I should fix the code. My problem is that I need my code to write into the Path three different paths for Logstash, Kibana and ElasticSearch, but I have no idea how to do it. It returns always the same error about missing ")" error Here's the whole code ¨ [CmdletBinding(SupportsShouldProcess=$true)] param( [string]$NewLocation.GetType($ElasticSearch) [string]$ElasticSearch = "C:\Elastic_Test_Server\elasticsearch\bin" [string]$Kibana = "C:\Elastic_Test_Server\kibana\bin" [string]$Logstash = "C:\Elastic_Test_Server\logstash\bin" ) Begin { #Je potřeba spustit jako Administrátor $regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" $hklm = [Microsoft.Win32.Registry]::LocalMachine Function GetOldPath() { $regKey = $hklm.OpenSubKey($regPath, $FALSE) $envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) return $envPath } } Process { # Win32API errory $ERROR_SUCCESS = 0 $ERROR_DUP_NAME = 34 $ERROR_INVALID_DATA = 13 $NewLocation = $NewLocation.Trim(); If ($NewLocation -eq "" -or $NewLocation -eq $null) { Exit $ERROR_INVALID_DATA } [string]$oldPath = GetOldPath Write-Verbose "Old Path: $oldPath" # Zkontroluje zda cesta již existuje $parts = $oldPath.split(";") If ($parts -contains $NewLocation) { Write-Warning "The new location is already in the path" Exit $ERROR_DUP_NAME } # Nová cesta $newPath = $oldPath + ";" + $NewLocation $newPath = $newPath -replace ";;","" if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){ # Přidá to přítomné session $env:path += ";$NewLocation" # Uloží do registru $regKey = $hklm.OpenSubKey($regPath, $True) $regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString) Write-Output "The operation completed successfully." } Exit $ERROR_SUCCESS } Thank you for your help.
I really think you could simplify this a lot, unless I have misunderstood. Apologies, I am not currently on a Windows machine so can't test this. function Add-ESPath { # Create an array of the paths we wish to add. $ElasticSearch = #( "C:\Elastic_Test_Server\elasticsearch\bin", "C:\Elastic_Test_Server\kibana\bin", "C:\Elastic_Test_Server\logstash\bin" ) # Collect the current PATH string and split it out in to an array $CurrentPath = [System.Environment]::GetEnvironmentVariable("PATH") $PathArray = $CurrentPath -split ";" # Loop though the paths we wish to add. foreach ($Item in $ElasticSearch) { if ($PathArray -notcontains $Item) { $PathArray += $Item } else { Write-Output -Message "$Item is already a member of the path." # Use Write-Warning if you wish. I see it more as a notification here. } } # Set the path. $PathString = $PathArray -join ";" Try { [System.Environment]::SetEnvironmentVariable("PATH", $PathString) exit 0 } Catch { Write-Warning -Message "There was an issue setting PATH on this machine. The path was:" # Use $env:COMPUTERNAME here perhaps instead of 'this machine'. Write-Warning -Message $PathString Write-Warning -Message $_.Exception.Message exit 1 } } Add-ESPath Perhaps you want to add some kind of log file rather than writing messages/warnings to the console. You can use Add-Content for this.
I long time ago i wrote some functions to add a path to system path + their is an check if the path is already inside the system path. And i also did an elevation check so when i use this function and i forgot to elevate my powershell that i get a warning. Its a different approach, I hope it will help you. I only use the begin {} proccess{} statements for when i want to write a function that excepts pipeline inputs. So its if you want to write a function that will work as the following: $paths = #("C:\Elastic_Test_Server\elasticsearch\bin", "C:\Elastic_Test_Server\kibana\bin") $paths | my-append-these-to-system-path-function Elevation check: function G-AmIelevated($warningMessage){ if([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")){ return $true }else{ write-host "not elevated $warningMessage" -ForegroundColor Red return $false } } append something to system path with check if its already inside system path: function G-appendSystemEnvironmentPath($str){ if(test-path $str){ if(!((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path) -like "*$str*")){ write-host "`t $str exists...`n adding $str to environmentPath" -ForegroundColor Yellow if(G-AmIelevated){ write-host `t old: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' ` -Name Path ` -Value "$((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path);$str" write-host `t new: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path write-host `t restart the computer for the changes to take effect -ForegroundColor Red write-host `t `$Env:Path is the merge of System Path and User Path This function set the system path write-host `t $str appended to environmet variables. -ForegroundColor Green }else{ write-host `t rerun ise in elevated mode -ForegroundColor Red } }else{ write-host "`t $str is in system environmenth path" } }else{ write-host `t $str does not exist } } G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\elasticsearch\bin" G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\kibana\bin" G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\logstash\bin"
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" + $_ } } }
Multiple foreground colors in PowerShell in one command
I want to output many different foreground colors with one statement. PS C:\> Write-Host "Red" -ForegroundColor Red Red This output is red. PS C:\> Write-Host "Blue" -ForegroundColor Blue Blue This output is blue. PS C:\> Write-Host "Red", "Blue" -ForegroundColor Red, Blue Red Blue This output is magenta, but I want the color to be red for the word red, and blue for the word blue via the one command. How can I do that?
You could roll your own Write-Color command or something that looks for inline tokens that change the color. This is how ANSI escape sequences used to work back in the BBS days. But you could achieve what you want by doing: Write-Host "Red " -f red -nonewline; Write-Host "Blue " -f blue; Here's a simple little function that does what you asked. function Write-Color([String[]]$Text, [ConsoleColor[]]$Color) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -Foreground $Color[$i] -NoNewLine } Write-Host } Write-Color -Text Red,White,Blue -Color Red,White,Blue
Edit (7th May 2018): I've updated Write-Color to 0.5 and published it as module. Also code is now published on github. Changes in 0.5: added backgroundcolor added aliases T/B/C to shorter code added alias to function (can be used with “WC”) fixes to module publishing Changes in 0.4 fixed small issues published as module Links to resources: GitHub Repository GitHub Reporting Issues PowerShellGallery Module Module description and a starting point. Thanks to published module you can easily use the code as below: Install-Module PSWriteColor Write-Color -Text "Some","Text" -Color Yellow,Red There is no more need to copy/paste code. Enjoy. Old code is below. It's highly advised to use links above for newest code: Edit (9th April 2018): I've updated Write-Color to v0.3. Feel free to get it at my site where I'm maintaining Write-Color. There are few small changes. Inluded -NoNewLine and -ShowTime option. Edit (Jun 2017): updated with new version, added logging to file for logging purposes Josh method was so great that I actually went and expanded it a bit for my needs. I've written blog post How to format PowerShell with Multiple Colors about it (with screenshots and all - for the whole story and usage). function Write-Color([String[]]$Text, [ConsoleColor[]]$Color = "White", [int]$StartTab = 0, [int] $LinesBefore = 0,[int] $LinesAfter = 0, [string] $LogFile = "", $TimeFormat = "yyyy-MM-dd HH:mm:ss") { # version 0.2 # - added logging to file # version 0.1 # - first draft # # Notes: # - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx $DefaultColor = $Color[0] if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host "`n" -NoNewline } } # Add empty line before if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host "`t" -NoNewLine } } # Add TABS before text if ($Color.Count -ge $Text.Count) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } } else { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $DefaultColor -NoNewLine } } Write-Host if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host "`n" } } # Add empty line after if ($LogFile -ne "") { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } Write-Output "[$([datetime]::Now.ToString($TimeFormat))]$TextToFile" | Out-File $LogFile -Encoding unicode -Append } } Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" It actually brings additional checks and features over Josh script.
This function provides different syntactic sugar: function color-Write { # DO NOT SPECIFY param(...) # we parse colors ourselves. $allColors = ("-Black", "-DarkBlue","-DarkGreen","-DarkCyan","-DarkRed","-DarkMagenta","-DarkYellow","-Gray", "-Darkgray","-Blue", "-Green", "-Cyan", "-Red", "-Magenta", "-Yellow", "-White") $foreground = (Get-Host).UI.RawUI.ForegroundColor # current foreground $color = $foreground [bool]$nonewline = $false $sofar = "" $total = "" foreach($arg in $args) { if ($arg -eq "-nonewline") { $nonewline = $true } elseif ($arg -eq "-foreground") { if ($sofar) { Write-Host $sofar -foreground $color -nonewline } $color = $foregrnd $sofar = "" } elseif ($allColors -contains $arg) { if ($sofar) { Write-Host $sofar -foreground $color -nonewline } $color = $arg.substring(1) $sofar = "" } else { $sofar += "$arg " $total += "$arg " } } # last bit done special if (!$nonewline) { Write-Host $sofar -foreground $color } elseif($sofar) { Write-Host $sofar -foreground $color -nonewline } } Examples: color-Write This is normal text color-Write Normal -Red Red -White White -Blue Blue -ForeGround Normal
Here is small a function I wrote to output colored text (it is actually smaller, but I rewrote it to be more understandable): function Write-Color() { Param ( [string] $text = $(Write-Error "You must specify some text"), [switch] $NoNewLine = $false ) $startColor = $host.UI.RawUI.ForegroundColor; $text.Split( [char]"{", [char]"}" ) | ForEach-Object { $i = 0; } { if ($i % 2 -eq 0) { Write-Host $_ -NoNewline; } else { if ($_ -in [enum]::GetNames("ConsoleColor")) { $host.UI.RawUI.ForegroundColor = ($_ -as [System.ConsoleColor]); } } $i++; } if (!$NoNewLine) { Write-Host; } $host.UI.RawUI.ForegroundColor = $startColor; } It's quite simple to use: just use Write-Color "your text" and add some color name between curly brackets where you want the text to be colored. Examples: `Write-Color "Hello, {red}my dear {green}friend !"` will output Script screenshot You can put it in your $profile file to use it in a simple PowerShell prompt, or just add it to some scripts.
I found a much easier option at https://blogs.technet.microsoft.com/heyscriptingguy/2011/05/17/writing-output-with-powershell/ Basically, the first write-host includes the option -NoNewLine. This prevents the new line from forming. The next write-host will be added immediately after the previous text. And each of the separate write-host can have -foregroundcolor options. That can be repeated for each color change you need. Example with one line of text with three colors: write-host "Your text here " -ForeGroundColor Red -NoNewLine write-host "some other text here " -ForeGroundColor Yellow -NoNewLine write-host "And the last text here." Notice that there is a space after the text in the first and second write-host. PowerShell is not concatenating or combining the text, it is simply not moving the cursor to the next line.
Find advanced function Write-HostColored below, which allows embedding coloring instructions in a string, both for the foreground and the background color: Write-HostColored "I'm #green#green#, I'm #red#red#, and I'm #blue:white#blue on white#." The above yields: In addition to accepting a default foreground and background color with -ForegroundColor and -BackgroundColor, you can embed one or more color specifications in the string to write, using the following syntax: #<fgcolor>[:<bgcolor>]#<text># <fgcolor> and <bgcolor> must be valid [ConsoleColor] values, such as green or white (case does not matter). Everything following the color specification up to the next #, or implicitly up to the end of the string, is written in that color. Write-HostColored source code (PSv2+): <# .SYNOPSIS A wrapper around Write-Host that supports selective coloring of substrings via embedded coloring specifications. .DESCRIPTION In addition to accepting a default foreground and background color, you can embed one or more color specifications in the string to write, using the following syntax: #<fgcolor>[:<bgcolor>]#<text># <fgcolor> and <bgcolor> must be valid [ConsoleColor] values, such as 'green' or 'white' (case does not matter). Everything following the color specification up to the next '#', or impliclitly to the end of the string, is written in that color. Note that nesting of color specifications is not supported. As a corollary, any token that immediately follows a color specification is treated as text to write, even if it happens to be a technically valid color spec too. This allows you to use, e.g., 'The next word is #green#green#.', without fear of having the second '#green' be interpreted as a color specification as well. .PARAMETER ForegroundColor Specifies the default text color for all text portions for which no embedded foreground color is specified. .PARAMETER BackgroundColor Specifies the default background color for all text portions for which no embedded background color is specified. .PARAMETER NoNewline Output the specified string withpout a trailing newline. .NOTES While this function is convenient, it will be slow with many embedded colors, because, behind the scenes, Write-Host must be called for every colored span. .EXAMPLE Write-HostColored "#green#Green foreground.# Default colors. #blue:white#Blue on white." .EXAMPLE '#black#Black on white (by default).#Blue# Blue on white.' | Write-HostColored -BackgroundColor White #> function Write-HostColored() { [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [string[]] $Text , [switch] $NoNewline , [ConsoleColor] $BackgroundColor = $host.UI.RawUI.BackgroundColor , [ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor ) begin { # If text was given as a parameter value, it'll be an array. # Like Write-Host, we flatten the array into a single string # using simple string interpolation (which defaults to separating elements with a space, # which can be changed by setting $OFS). if ($Text -ne $null) { $Text = "$Text" } } process { if ($Text) { # Start with the foreground and background color specified via # -ForegroundColor / -BackgroundColor, or the current defaults. $curFgColor = $ForegroundColor $curBgColor = $BackgroundColor # Split message into tokens by '#'. # A token between to '#' instances is either the name of a color or text to write (in the color set by the previous token). $tokens = $Text.split("#") # Iterate over tokens. $prevWasColorSpec = $false foreach($token in $tokens) { if (-not $prevWasColorSpec -and $token -match '^([a-z]*)(:([a-z]+))?$') { # a potential color spec. # If a token is a color spec, set the color for the next token to write. # Color spec can be a foreground color only (e.g., 'green'), or a foreground-background color pair (e.g., 'green:white'), or just a background color (e.g., ':white') try { $curFgColor = [ConsoleColor] $matches[1] $prevWasColorSpec = $true } catch {} if ($matches[3]) { try { $curBgColor = [ConsoleColor] $matches[3] $prevWasColorSpec = $true } catch {} } if ($prevWasColorSpec) { continue } } $prevWasColorSpec = $false if ($token) { # A text token: write with (with no trailing line break). # !! In the ISE - as opposed to a regular PowerShell console window, # !! $host.UI.RawUI.ForegroundColor and $host.UI.RawUI.ForegroundColor inexcplicably # !! report value -1, which causes an error when passed to Write-Host. # !! Thus, we only specify the -ForegroundColor and -BackgroundColor parameters # !! for values other than -1. # !! Similarly, PowerShell Core terminal windows on *Unix* report -1 too. $argsHash = #{} if ([int] $curFgColor -ne -1) { $argsHash += #{ 'ForegroundColor' = $curFgColor } } if ([int] $curBgColor -ne -1) { $argsHash += #{ 'BackgroundColor' = $curBgColor } } Write-Host -NoNewline #argsHash $token } # Revert to default colors. $curFgColor = $ForegroundColor $curBgColor = $BackgroundColor } } # Terminate with a newline, unless suppressed if (-not $NoNewLine) { write-host } } }
This code is available with a different number of arguments: Text, ForeGroundColor, and BackGroundColor. Each colorlist is used with a rotate implementation: function Write-Color([String[]]$Text, [ConsoleColor[]]$ForeGroundColor, [ConsoleColor[]]$BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { $Color = #{} if ($ForeGroundColor -and $BackGroundColor){ $Color = #{ ForegroundColor = $ForeGroundColor[$i%($ForeGroundColor.count)] BackgroundColor = $BackGroundColor[$i%($BackGroundColor.count)] } } elseif ($ForeGroundColor) { $Color = #{ ForegroundColor = $ForeGroundColor[$i%($ForeGroundColor.count)] } } elseif ($BackGroundColor) { $Color = #{ BackgroundColor = $BackGroundColor[$i%($BackGroundColor.count)] } } Write-Host $Text[$i] #color -NoNewLine } Write-Host } Log usage: Write-Color "Check color list...".PadRight(50), '[', ' OK ', ']' -fore cyan, White, green, white Write-Color "Red Check is good...".PadRight(50), '[' ,' ERROR! ', ']' -fore cyan, White, red, white Write-Color "Write-Color is cool !".PadRight(50), '[', ' WARN ', ']' -fore cyan, White, Yellow, white List Usage (just 2 backGroundColor and 4 foreGroundColor): Write-Color (#(100..115) | %{" -> $_ : ".PadRight(30) + "`n"}) -ForeGroundColor cyan, yellow, magenta, red -BackGroundColor gray, black Standard Write-Host Write-Host (#(100..115) | %{" -> $_ : ".PadRight(30) + "`n"}) -BackgroundColor gray
This works too... Write-Host "Don't forget to " -ForegroundColor Yellow -NoNewline; Write-Host "CALL YOUR MOM " -ForegroundColor Red -NoNewline; Write-Host "every day!" -ForegroundColor Yellow
Slight modification to this one... I took version 2, removed the logging (because I don't want it), and then added a Boolean parameter, similar to -NoNewLine for Write-Host. I was specifically trying to add the ability to change the colors and prompt for user input on the same line so that I could highlight the default answer if the user does not enter anything. I realize this was available in Write-HostColored (in a previous answer)... but sometimes you just want simpler code... function Write-Color([String[]]$Text, [ConsoleColor[]]$Color = "White", [int]$StartTab = 0, [int] $LinesBefore = 0,[int] $LinesAfter = 0, [bool] $NewLine = $True) { # Notes: # - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx # # Example: Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow -NewLine $False # $DefaultColor = $Color[0] if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host "`n" -NoNewline } } # Add empty line before if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host "`t" -NoNewLine } } # Add TABS before text if ($Color.Count -ge $Text.Count) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } } else { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $DefaultColor -NoNewLine } } if ($NewLine -eq $False) { Write-Host -NoNewLine } else { Write-Host } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host "`n" } } # Add empty line after } # END FUNCTION Write-Color Sample of what I was trying to accomplish: Write-Color -Text "Is this correct? ","[y]","/n" -Color White, Magenta, White -NewLine $False ; Read-Host " "
So here's something I came up with. Hope it helps someone out. $e = "$([char]27)" enum ANSIFGColors { Black = 30 Red = 91 Green = 92 Yellow = 93 Blue = 94 Magenta = 95 Cyan = 96 White = 97 } enum ANSIBGColors { Black = 40 Red = 41 Green = 42 Yellow = 103 Blue = 44 Magenta = 105 Cyan = 46 White = 107 } function Colorize-Text { param ( [string]$StringToColor, [ANSIFGColors]$TextColor, [ANSIBGColors]$BackgroundColor ) $retValue = $null if ($BackgroundColor -ne $null ) { $retValue = [string]"$e[$($TextColor.value__);$($BackgroundColor.value__)m$StringToColor$e[0m" } else { $retValue = [string]"$e[$($TextColor.value__)m$StringToColor$e[0m" } return $retValue } Can be used thus; $FirstVar = Colorize-Text -StringToColor "This is Green" -TextColor Green $SecondVar = Colorize-Text -StringToColor "This is NOT Green" -TextColor Cyan -BackgroundColor Red Write-host $FirstVar $SecondVar Or whatever other combination you choose.
Here is a simplistic way to do this if ($help) { Write-Host " For the switch " -NoNewline; Write-Host " -userUniqueId" -ForegroundColor Green -NoNewline; Write-Host ", enter an email address or samaccountname (pin) so '-userUniqueId 123456' " Write-Host ""; Write-Host " For the switch " -NoNewline; Write-Host " -disableMFAForUser" -ForegroundColor Green -NoNewline; Write-Host ", enter an email address or samaccountname (pin) with the -userUniqueId and then '-disableMFAForUser $true' " Write-Host ""; Write-Host " For the switch " -NoNewline; Write-Host "-enableMFAForUser" -ForegroundColor Green -NoNewline; Write-Host ", enter an email address or samaccountname (pin) with the -userUniqueId and then '-enableMFAForUser $true' " Write-Host ""; Write-Host " For the switch " -NoNewline; Write-Host "-verifyAllMFAEnabled" -ForegroundColor Green -NoNewline; Write-Host ", enter '-verifyAllMFAEnabled $true' " Write-Host ""; Write-Host " For the switch " -NoNewline; Write-Host " -verifyAllMFADisabledSpecificUser" -ForegroundColor Green -NoNewline; Write-Host ", enter an email address or samaccountname (pin) and then '-verifyAllMFADisabledSpecificUser $true' " Write-Host ""; return; }
I was trying to run this on a Windows Server 2012R2 box under ISE and the function by Jesse Chisholm was failing because for some reason (Get-Host).UI.RawUII.ForegroundColor was -1. To stop this happening and to simplify the function I little I modified it as follows: function Write-ColorText { # DO NOT SPECIFY param(...) # we parse colors ourselves. $allColors = ("-Black", "-DarkBlue","-DarkGreen","-DarkCyan","-DarkRed","-DarkMagenta","-DarkYellow","-Gray", "-Darkgray","-Blue", "-Green", "-Cyan", "-Red", "-Magenta", "-Yellow", "-White", "-Foreground") $color = "Foreground" $nonewline = $false foreach($arg in $args) { if ($arg -eq "-nonewline") { $nonewline = $true } elseif ($allColors -contains $arg) { $color = $arg.substring(1) } else { if ($color -eq "Foreground") { Write-Host $arg -nonewline } else { Write-Host $arg -foreground $color -nonewline } } } Write-Host -nonewline:$nonewline } I know this is an old post but hopefully this is useful to somebody and thanks Jesse for giving me this wonderful function!!
If you are in my situation, I've found a plain vanilla way from microsoft docs to set console mode. So here is an easy way to start and end 256 colors ansi console support in both cmd and powershell: // https://learn.microsoft.com/en-us/windows/console/setconsolemode #include <Windows.h> #include <iostream> struct console_data { HANDLE hstdin; DWORD mode; DWORD start() { hstdin = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleMode(hstdin, &mode); if (!SetConsoleMode(hstdin, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { DWORD E = GetLastError(); std::cout << " Error #" << E << "in:" __FUNCTION__ "\n"; return GetLastError(); } std::cout << "\033[1;14mReady.\e[0m\n"; return 0; } void end() { SetConsoleMode(hstdin, mode); } ~console_data() { end(); } //... }RTConsole; //... int main() { //... RTConsole.start(); std::cout << "\033[38;5;192m Should be 'Orange'\n"; RTConsole.end(); return 0; } https://learn.microsoft.com/en-us/windows/console/setconsolemode Note: I did not find how to get 256 colors support into VS Code's poswershell extension.
Yet another "write color" function, this one: Has a helper function Get-WriteColors to help make a color array. (Otherwise you have to do [ConsoleColor]::White, [ConsoleColor]::Red, etc...) Repeatedly cycles independently through both ForeColor and BackColor. By default uses the '~' marker in the text to indicate a color change. If given 2 strings before color parameters, the second string replaces '~' as the color change marker. Includes a -NoNewLine switch. function Get-WriteColors { param( [Parameter(Mandatory, Position = 0)] [ConsoleColor[]]$ColorSet ) return $ColorSet } function Write-Colors { [CmdletBinding(DefaultParameterSetName = 'NoCCMarker')] param( [Parameter(Mandatory, Position = 0)] [string]$Text, [Parameter(Mandatory, Position = 1, ParameterSetName = 'HasCCMarker')] [string]$ColorChangeMarker, [Parameter(Mandatory, Position = 1, ParameterSetName = 'NoCCMarker')] [Parameter(Mandatory, Position = 2, ParameterSetName = 'HasCCMarker')] [ConsoleColor[]]$ForeColor, [Parameter(Position = 2, ParameterSetName = 'NoCCMarker')] [Parameter(Position = 3, ParameterSetName = 'HasCCMarker')] [ConsoleColor[]]$BackColor = #([ConsoleColor]::Black), [switch]$NoNewLine ) $Marker = if($PsCmdlet.ParameterSetName -eq 'NoCCMarker') {'~'} else {$ColorChangeMarker} $TextList = $Text -Split $Marker for($t = 0; $t -lt $TextList.Count; $t++) { $f = $t % $ForeColor.Count $b = $t % $BackColor.Count Write-Host $TextList[$t] -ForegroundColor $ForeColor[$f] -BackgroundColor $BackColor[$b] -NoNewLine } if( -not $NoNewLine ) {Write-Host} } Example use, the second call to Write-Colors replaces the default '~' with '#': $f = 'C:\Temp\TestPath\TestFile.TXT' $d =[System.IO.Path]::GetPathRoot($f) $p = [System.IO.Path]::GetDirectoryName($f).SubString($d.Length) $n = [System.IO.Path]::GetFileNameWithoutExtension($f) $x = [System.IO.Path]::GetExtension($f) $dp = $d + $p $nx = $n + $x # Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray # DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White $ForeColors = Get-WriteColors Yellow, White, Cyan, Yellow, Red $BackColors = Get-WriteColors DarkBlue, black, black, black, black Write-Colors "f:~ [~$f~]" $ForeColors $BackColors Write-Colors "dp:#[#$dp#]#, #nx:#[#$nx#]" '#' $ForeColors $BackColors Write-Colors "d:~ [~$d~]~, ~p:~[~$p~]~, ~n:~[~$n~]~, ~x:~[~$x~]" $ForeColors $BackColors Output: