PSChristmasTree.psm1
### --- PUBLIC FUNCTIONS --- ### #region - Show-PSChristmasTree.ps1 function Show-PSChristmasTree() { <# .Synopsis Display a christmas tree .Description Returns a christmas tree with decorations that lights up. It has many parameters to customize it .Parameter AnimationLoopNumber Number of times to loop animation .Parameter AnimationSpeed Time in milliseconds to show each frame .Parameter Colors All foreground colors possibilities you want to use (Array) .Parameter Decorations Hashtable : Key => character you want to animate Value => color you want to display this character .Parameter PlayCarol Number of times to loop "we wish you a merry christmas" carol .Parameter UICulture Set the language code in order to get it in locales, if it does not exist in locales, use the default one (en-US) .Example # Show christmas tree by playing "we wish you a merry christmas" carol once Show-PSChristmasTree -PlayCarol 1 .LINK https://github.com/Sofiane-77/PSChristmasTree #> [CmdletBinding()] [OutputType([System.void])] Param ( [Parameter( Mandatory=$false, Position=0 )] [ValidateRange(1,[int]::MaxValue)] [int]$AnimationLoopNumber=50, [Parameter( Mandatory=$false, Position=1 )] [ValidateRange(1,[int]::MaxValue)] [int]$AnimationSpeed=300, [Parameter( Mandatory=$false, Position=2 )] [ValidateNotNullOrEmpty()] [array]$Colors=@('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White'), [Parameter( Mandatory=$false, Position=3 )] [ValidateNotNullOrEmpty()] [hashtable]$Decorations=@{}, [Parameter( Mandatory=$false, Position=4 )] [ValidateRange(0,[int]::MaxValue)] [int]$PlayCarol=0, [Parameter( Mandatory=$false, Position=5 )] [ValidateNotNullOrEmpty()] [string]$UICulture=(Get-UICulture).Name ) BEGIN { # Save current value $CurrentColor = Get-ConsoleForegroundColor $CurrentBufferSize = Get-BufferSizeWidth $CurrentCursorSize = Get-CursorSize $ChristmasTree = Get-ChristmasTree $ChristmasDecorations = @{ 'O' = 'random'; "$($ChristmasTree.trunk)" = 'red'; } # See the comment for the decorations parameter $ChristmasDecorations = Merge-Hashtable $ChristmasDecorations $Decorations $ChristmasTree.tree = Get-CenteredText -Text $ChristmasTree.tree # Center in terminal $ChristmasTree.tree = Add-DecorationTag -Text $ChristmasTree.tree -Decorations $ChristmasDecorations $Messages = Import-LocalizedData -BaseDirectory (Join-Path -Path $PSScriptRoot -ChildPath "./locales") -FileName "Messages.psd1" -UICulture $UICulture -ErrorAction:SilentlyContinue $Messages.MerryChristmas.Text = $Messages.MerryChristmas.Text.ToUpper(); $Messages.MessageForDevelopers.Text = $Messages.MessageForDevelopers.Text.replace("{1}", (Get-NewYear)); $Messages.HappyNewYear.Text = $Messages.HappyNewYear.Text.ToUpper(); Hide-CursorSize } PROCESS { Invoke-Carol $PlayCarol $i = 0 do { if ($CurrentBufferSize -ine (Get-BufferSizeWidth)) { $ChristmasTree.tree = Get-CenteredText -Text (Get-ChristmasTree).tree # Re-center the original tree $ChristmasTree.tree = Add-DecorationTag -Text $ChristmasTree.tree -Decorations $ChristmasDecorations $CurrentBufferSize = Get-BufferSizeWidth Hide-CursorSize } Clear-Host Write-Host-Colorized -DecoratedText $ChristmasTree.tree -Colors $Colors -DefaultForegroundColor 'Green' # Color of the chrismas tree Write-Host (Get-CenteredText -Text $Messages.MerryChristmas.Text) -ForegroundColor ($Messages.MerryChristmas.Colors | Get-Random) Write-Host-Colorized (Get-DecoratedFormattedText -FormattedText $Messages.MessageForDevelopers.Text -FormattedValue $Messages.MessageForDevelopers.'{0}' -Centered $true) -Colors $Colors -DefaultForegroundColor $Messages.MessageForDevelopers.Color Write-Host (Get-CenteredText -Text $Messages.HappyNewYear.Text) -ForegroundColor ($Messages.HappyNewYear.Colors | Get-Random) Start-Sleep -Milliseconds $AnimationSpeed $i++ } until ($i -eq $AnimationLoopNumber) } END { # We dont need to set CursorSize if the value is null if (![string]::IsNullOrWhitespace($CurrentCursorSize)) { Set-CursorSize $CurrentCursorSize } Set-ConsoleForegroundColor $CurrentColor } } Export-ModuleMember -Function Show-PSChristmasTree #endregion ### --- PRIVATE FUNCTIONS --- ### #region - Add-DecorationTag.ps1 function Add-DecorationTag() { <# .Synopsis Decorate Text with Tags to be able to add color to specific character .Description Replace character with #color#character# pattern .Parameter Text Text to decorate .Parameter Decorations Hashtable : Key => character you want to animate Value => color you want to display this character .Example # Decorate "Hello World!" for "o" character to be red Add-DecorationTag "Hello World!" @{'o'='red'} Result : Hell#red#o# W#red#o#rld! #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateNotNullOrEmpty()] [string]$Text, [Parameter( Mandatory = $true, Position=1 )] [ValidateNotNullOrEmpty()] [Hashtable]$Decorations ) BEGIN {} PROCESS { foreach ($decoration in $Decorations.keys) { $Text = $Text.Replace("$decoration", "#$($Decorations.$decoration)#$decoration#") } } END { return $Text } } #endregion #region - Get-BufferSizeWidth.ps1 function Get-BufferSizeWidth() { <# .Synopsis Buffer size width of terminal .Description Return BufferSize of terminal #> return $Host.UI.RawUI.BufferSize.Width } #endregion #region - Get-CenteredText.ps1 function Get-CenteredText() { <# .Synopsis Center a text in console .Description Returns the text with amount of space needed to be centered on each line .Parameter Text Text to center .Example # Center "Hello World!". Center-Text "Hello World!" #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter( Mandatory = $true, Position=0, ValueFromPipeline=$true )] [ValidateNotNullOrEmpty()] [string]$Text ) BEGIN { $CenteredString = [System.Collections.ArrayList]::new() # Array will contains each line centered } PROCESS { foreach ($line in $Text -split [System.Environment]::NewLine) { $line = $line.Trim() [void]$CenteredString.Add(("{0}{1}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($line.Length / 2)))), $line)) } } END { return $CenteredString -Join [System.Environment]::NewLine # Join each line to return a string } } #endregion #region - Get-ChristmasTree.ps1 function Get-ChristmasTree() { <# .Synopsis Christmas tree .Description Return hashtable with christmas tree and trunk #> return @{ 'tree' = @" | -+- A /=\ i/ O \i /=====\ / i \ i/ O * O \i /=========\ / * * \ i/ O i O \i /=============\ / O i O \ i/ * O O * \i /=================\ |___| "@; 'trunk' = '|___|'; # Trunk of christmas tree } } #endregion #region - Get-ConsoleForegroundColor.ps1 function Get-ConsoleForegroundColor() { <# .Synopsis Foreground color of console .Description Foreground color of console #> return [Console]::ForegroundColor } #endregion #region - Get-CursorSize.ps1 function Get-CursorSize() { <# .Synopsis Cursor size .Description Return cursor size of terminal #> # we tried [Console]::CursorSize but it make exception return $HOST.UI.RawUI.CursorSize } #endregion #region - Get-DecoratedFormattedText.ps1 function Get-DecoratedFormattedText() { <# .Synopsis Decorate formatted text .Description Returns decorated formatted text .Parameter FormattedText Formatted Text .Parameter FormattedValue Value to put inside FormattedText .Parameter Centered Center Text .Example # Add decoration for World Get-DecoratedFormattedText -FormattedText "Hello {0}!" -FormattedValue "World" -Centered $false Result : Hello #random#World#! #> [CmdletBinding()] [OutputType([String])] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateNotNullOrEmpty()] [string]$FormattedText, [Parameter( Mandatory = $true, Position=1 )] [ValidateNotNullOrEmpty()] [string]$FormattedValue, [Parameter( Mandatory = $false, Position=2 )] [boolean]$Centered=$false ) BEGIN { $Text = "" if ($Centered) { $FormattedText = (Get-CenteredText -Text ($FormattedText -f $FormattedValue)).Split($FormattedText[0])[0] + $FormattedText } } PROCESS { $decorations = @{ "$FormattedValue" = 'random' } $Text = Add-DecorationTag -Text $FormattedValue -Decorations $decorations } END { return $FormattedText -f $Text } } #endregion #region - Get-NewYear.ps1 function Get-NewYear() { <# .Synopsis Get Year for christmas messages .Description Returns the current year or new year depends on when you call it #> [OutputType([int])] [int]$currentYear = Get-Date -UFormat "%Y" # The following year is displayed only if the date is greater than December 23 if ((Get-Date) -gt (Get-Date -Year $currentYear -Month 12 -Day 23)) { $newYear = $currentYear + 1 return $newYear } return $currentYear } #endregion #region - Hide-CursorSize.ps1 function Hide-CursorSize() { <# .Synopsis Hide cursor size of terminal .Description Hide cursor size of terminal .Example # Hide cursor size Hide-CursorSize #> return Set-CursorSize 1 # Hide the cursor before display the tree } #endregion #region - Invoke-Carol.ps1 function Invoke-Carol() { <# .Synopsis Play "we wish you a merry christmas" .Description Play the carol in antoher thread .Parameter CarolLoopNumber Number of carol repetitions .Example # Play the carol 1 time Invoke-Carol 1 #> [CmdletBinding()] [OutputType([System.Void])] Param ( [Parameter( Mandatory = $false, Position=0 )] [ValidateRange(0,[int]::MaxValue)] [int]$CarolLoopNumber=1 ) BEGIN { # We created another thread to be able to play the song AND display the tree $Runspace = [runspacefactory]::CreateRunspace() $PowerShell = [powershell]::Create() $PowerShell.Runspace = $Runspace $Runspace.Open() $Runspace.SessionStateProxy.SetVariable('CarolLoopNumber', $CarolLoopNumber) # Share parameter to other powershell } PROCESS { $PowerShell.AddScript({ For($i=0;$i -lt $CarolLoopNumber;$i++) { $Duration = @{ WHOLE = 1600; } $Duration.HALF = $Duration.WHOLE/2; $Duration.QUARTER = $Duration.HALF/2; $Duration.EIGHTH = $Duration.QUARTER/2; $Duration.SIXTEENTH = $Duration.EIGHTH/2; # Hashtable where key = note and value = frequency $Notes = @{ A4 = 440; B4 = 493.883301256124; C5 = 523.251130601197; D5 = 587.329535834815; E5 = 659.25511382574; F5 = 698.456462866008; G4 = 391.995435981749; } [console]::beep($Notes.G4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.C5,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.C5,$Duration.EIGHTH) [console]::beep($Notes.D5,$Duration.EIGHTH) [console]::beep($Notes.C5,$Duration.EIGHTH) [console]::beep($Notes.B4,$Duration.EIGHTH) [console]::beep($Notes.A4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.A4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.A4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.D5,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.D5,$Duration.EIGHTH) [console]::beep($Notes.E5,$Duration.EIGHTH) [console]::beep($Notes.D5,$Duration.EIGHTH) [console]::beep($Notes.C5,$Duration.EIGHTH) [console]::beep($Notes.B4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.G4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.G4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.E5,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.E5,$Duration.EIGHTH) [console]::beep($Notes.F5,$Duration.EIGHTH) [console]::beep($Notes.E5,$Duration.EIGHTH) [console]::beep($Notes.D5,$Duration.EIGHTH) [console]::beep($Notes.C5,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.A4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.G4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.A4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.D5,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.B4,$Duration.EIGHTH) Start-Sleep -m $Duration.SIXTEENTH [console]::beep($Notes.C5,$Duration.EIGHTH) } }) $PowerShell.BeginInvoke() } END {} } #endregion #region - Merge-Hashtable.ps1 Function Merge-Hashtable() { <# .Synopsis Merge Hashtable .Description Return a merged Hashtable #> [OutputType([Hashtable])] $Output = @{} foreach ($Hashtable in $Args) { If ($Hashtable -is [Hashtable]) { foreach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key} } } return $Output } #endregion #region - Remove-DecorationTag.ps1 function Remove-DecorationTag() { <# .Synopsis Remove Tags for decorated Text .Description Replace #color#character# pattern with character .Parameter DecoratedText Decorated Text with Add-DecorationTags .Parameter Decorations Hashtable : Key => character you have chosen to animate Value => color you have chosen to display this character .Example # Remove tags for decorated Text Remove-DecorationTag "Hell#red#o# W#red#o#rld!" @{'o'='red'} Result : Hello World! #> [CmdletBinding(SupportsShouldProcess)] [OutputType([String])] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateNotNullOrEmpty()] [string]$DecoratedText, [Parameter( Mandatory = $true, Position=1 )] [ValidateNotNullOrEmpty()] [Hashtable]$Decorations ) BEGIN { $Text = "" } PROCESS { if($PSCmdlet.ShouldProcess($DecoratedText)){ foreach ($decoration in $Decorations.keys) { $Text = $DecoratedText.Replace("#$($Decorations.$decoration)#$decoration#", "$decoration") } } } END { return $Text } } #endregion #region - Set-ConsoleForegroundColor.ps1 function Set-ConsoleForegroundColor() { <# .Synopsis Set foreground color of console .Description Set foreground color of console .Parameter Color new value of console foreground color .Example # Set foreground color to gray Set-ConsoleForegroundColor "Gray" #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateNotNullOrEmpty()] [System.ConsoleColor]$Color ) BEGIN {} PROCESS { if($PSCmdlet.ShouldProcess('[Console]::ForegroundColor)')){ [Console]::ForegroundColor = $Color } } END {} } #endregion #region - Set-CursorSize.ps1 function Set-CursorSize() { <# .Synopsis Set cursor size of terminal .Description Set cursor size of terminal .Parameter Size new value of cursor size .Example # Set cursor size to 10 Set-CursorSize 10 #> [CmdletBinding(SupportsShouldProcess)] [OutputType([Bool])] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateRange(1,100)] [int]$Size ) BEGIN {} PROCESS { if($PSCmdlet.ShouldProcess('[Console]::CursorSize')){ try{ [Console]::CursorSize = $Size return $true } catch [System.PlatformNotSupportedException],[System.Management.Automation.SetValueInvocationException] { # The cursor size don't have setter on linux and macos return $false } } } END {} } #endregion #region - Write-Host-Colorized.ps1 function Write-Host-Colorized() { <# .Synopsis Write-Host with color .Description Display Text with color on host .Parameter DecoratedText Decorated Text with Add-DecorationTags .Parameter Colors List of all desired colors .Parameter DefaultForegroundColor Default text color when no color or decoration is specified .Example # Display "Hello World!" in white with red o Write-Host-Colorized "Hell#red#o# W#red#o#rld!" @('Red') "white" #> [CmdletBinding()] [OutputType([System.Void])] Param ( [Parameter( Mandatory = $true, Position=0 )] [ValidateNotNullOrEmpty()] [string]$DecoratedText, [Parameter( Mandatory = $true, Position=1 )] [ValidateNotNullOrEmpty()] [Array]$Colors, [Parameter( Mandatory = $true, Position=2)] [ValidateNotNullOrEmpty()] [string]$DefaultForegroundColor ) BEGIN { $currentColor = $DefaultForegroundColor $allColors = $Colors + 'Random' # Add random to the list of possible colors } PROCESS { # Iterate through splitted Messages foreach ( $string in $DecoratedText.split('#') ){ # If a string between #-Tags is equal to any predefined color, and is equal to the defaultcolor: set current color if ( $allColors -contains $string -and $currentColor -eq $DefaultForegroundColor ){ # if random chosen, we need to set a real color if ($string -ieq 'random') { $string = ($Colors | Get-Random) } $currentColor = $string }else{ # If string is a output message, than write string with current color (with no line break) Write-Host -nonewline -f $currentColor "$string" # Reset current color $currentColor = $DefaultForegroundColor } } } END { # Single write-host for the final line break Write-Host } } #endregion |