PSPrompt.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PSPrompt.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = $false # Get-PSFConfigValue -FullName PSPrompt.Import.DoDotSource -Fallback $false if ($PSPrompt_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = $false # Get-PSFConfigValue -FullName PSPrompt.Import.IndividualFiles -Fallback $false if ($PSPrompt_importIndividualFiles) { $importIndividualFiles = $true } #if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if (Test-Path "$($script:ModuleRoot)\..\.git") { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code #region code to execute every Nth command #TODO:: remove hard-coded 5 and replace with user option if ((Get-History -Count 1).ID % 5 -eq 0) { if ($BatteryHistory -or $Git) { #region Git status every Nth command $r = git status $NewFiles = ($r -match 'new file:').count $ModFiles = ($r -match 'modified:').count $DelFiles = ($r -match 'deleted:').count $Branch = $r[0] Write-Host "git status: " -ForegroundColor White -BackgroundColor Black -NoNewline if ($Branch -eq "On branch master") { Write-Host "*WARNING : Working in MASTER.*" -ForegroundColor Yellow -BackgroundColor Red -NoNewline } else { Write-Host "$r[0]" -ForegroundColor Blue -BackgroundColor Black -NoNewline } Write-Host " New[$NewFiles] " -ForegroundColor Green -BackgroundColor Black -NoNewline Write-Host " Mod[$ModFiles] " -ForegroundColor DarkCyan -BackgroundColor Black -NoNewline Write-Host " Del[$DelFiles] " -ForegroundColor Red -BackgroundColor Black #endregion #region BatteryHistory if ($BatteryHistory) { # not live yet so being kept apart #region BatteryHistory logging $b = (Get-CimInstance -ClassName CIM_Battery) Write-PSPLog -Message "$($b.EstimatedChargeRemaining)" -Source "BatteryPct" $Battery = @{ IsCharging = if ($b.BatteryStatus -eq 1) { $false } else { $true } Charge = $b.EstimatedChargeRemaining.GetValue(1) Remaining = $b.EstimatedRunTime.GetValue(1) } $msg = "Charging:{0}; ChargePct:{1}; Remaining:{2}" -f $Battery.IsCharging, ($Battery.Charge), $Battery.Remaining $msg Write-PSPLog -Message $msg -Source "BatteryStatus" #endregion #region BatteryHistory display $Hdr = "Date", "Source", "Message" $Hist = Import-Csv -Path "C:\temp\PSPLog.log" -Delimiter "`t" -Header $Hdr $BHist = $Hist | Where-Object source -eq "Batterypct" | Sort-Object date -Descending $Drain = $BHist[0, 1] | Select-Object date, message # $Drain[1].message - $Drain[0].message $Duration = New-TimeSpan -Start ([datetime]::parseexact($Drain[1].Date, "yyyyMMddHHmmss", $null)) -End ([datetime]::parseexact($Drain[0].Date, "yyyyMMddHHmmss", $null) ) $PowerLoss = ($Drain[1].message - $Drain[0].message) / $Duration.TotalMinutes # Percent loss per minute write-verbose ("Battery % loss per min {0:N1}" -f $PowerLoss) $msg ="[{0:N0}m]" -f ($Battery.Charge / $PowerLoss) Write-Host $msg #endregion } #endregion } } #endregion function Prompt { <# .Synopsis Your custom PowerShell prompt # origins from https://dbatools.io/prompt but formatting the execution time without using the DbaTimeSpanPretty C# type .Description Custom prompt that includes the following features: - Admin user : if the current session is running as Administrator then the prompt shows [Admin] in black on red - Battery : if you are working on your battery - % charged and est min remaining - Day and date : the current day and date are shown at the left side of the prompt in all use-cases - UTC offset : if the system timezone is not UTC then the offset in hours is shown Red/Green for + or - difference - fun function : put some fun into your PowerShell prompt with a countdown to your next big event () - current path : shortens the path if there are more than 2 directories and truncates those to 7 characters - last command : execution duration of the last command executed .Example Prompt There is no valid example for this function - it runs automatically as your console completes commands #> [outputtype([system.string])] [CmdletBinding()] #[CmdletBinding(DefaultParameterSetName = 'Custom')] Param( # [parameter(parametersetname = "Custom")][switch]$AddToProfile, # [parameter(parametersetname = "Custom")][switch]$Admin, # [parameter(parametersetname = "Custom")][switch]$Battery, # [parameter(ParameterSetName = "Reset")][switch]$Reset, # [parameter(ParameterSetName = "Custom")][switch]$Only # reserved for future use ) # if ($Reset) { # $PromptOptions = get-item "$env:APPDATA\prompt*.ps1" | Select-Object name, LastWriteTime # # Write-Output "Prompt returned to original state" # return # } #region Show if using Administrator level account #if ($admin) { $principal = [Security.Principal.WindowsPrincipal] ([Security.Principal.WindowsIdentity]::GetCurrent()) $adm = [Security.Principal.WindowsBuiltInRole]::Administrator if ($principal.IsInRole($adm)) { write-host -ForegroundColor "Black" -BackgroundColor "DarkRed" "[ADMIN]" -NoNewline } # "added admin" #} #endregion #region Battery status $b = (Get-CimInstance -ClassName CIM_Battery) $Battery = $null $Battery = @{ IsCharging = if ($b.BatteryStatus -eq 1) { $false } else { $true } Charge = $b.EstimatedChargeRemaining.GetValue(1) Remaining = $b.EstimatedRunTime.GetValue(1) } if ($Battery.Remaining -gt 90) { $Battery.Remaining = "90m+" } else { $Battery.Remaining = "$($Battery.Remaining)m" } Write-Verbose $Battery if (!($Battery.IsCharging)) { $msg = $b = $extramsg = $null switch ($Battery.Charge[0]) { { $_ -gt 80 } { $colour = "Green" break } { $_ -gt 60 } { $colour = "DarkGreen" break } { $_ -gt 40 } { $colour = "DarkRed" break } { $_ -gt 20 } { $colour = "Red" break } default { $extramsg = "`r`nBATTERY VERY LOW :: SAVE YOUR WORK`r`n" $colour = "yellow" } } $msg = ("[{0}%:{1}{2}]" -f ($Battery.Charge), ($Battery.Remaining), $EXTRAmsg ) Write-Host -Object $msg -BackgroundColor $colour -ForegroundColor Black -NoNewline } #endregion #region Day and date $msg = "[{0}]" -f (Get-Date -Format "ddd HH:mm:ss") Write-Host $msg -NoNewline #endregion #region UTC offset # add info if not in home timezone # get offset and daylight savings name $tz = Get-TimeZone $offset = $tz.BaseUtcOffset # need to place YOUR normal timezone here [timespan]$adjustment = 0 # if we are in daylight saving then the offset from home will be 60 mins, not 0 if ($tz.id -eq $tz.DaylightName) { $adjustment = New-TimeSpan -Minutes 60 } $fc = "white" $p = "GMT" if (($offset.TotalMinutes + $adjustment.TotalMinutes) -ne 0) { [double]$h = $offset.TotalMinutes / 60 if ($h -lt 0) { $sign = "" $fc = "Red" } else { $sign = "+" $fc = "Green" } $p = "(UK $sign$($h.ToString()))" write-host -ForegroundColor $fc -BackgroundColor Black $p -NoNewline } #endregion #region custom/fun function if ((get-date).Month -eq 12 -and (get-date).Day -lt 25) { $msg = "[" $msg += TimetoSanta -purpose Prompt $msg += "]" Write-Host $msg -NoNewline -BackgroundColor Red -ForegroundColor DarkBlue } #endregion #region last command execution duration try { $history = Get-History -ErrorAction Ignore -Count 1 if ($history) { $ts = New-TimeSpan $history.StartExecutionTime $history.EndExecutionTime Write-Host "[" -NoNewline switch ($ts) { { $_.TotalSeconds -lt 1 } { [decimal]$d = $_.TotalMilliseconds '{0:f3}ms' -f ($d) | Write-Host -ForegroundColor Black -NoNewline -BackgroundColor DarkGreen break } { $_.TotalMinutesutes -lt 1 } { [decimal]$d = $_.TotalSeconds '{0:f3}s' -f ($d) | Write-Host -ForegroundColor Black -NoNewline -BackgroundColor DarkYellow break } { $_.TotalMinutestes -lt 30 } { [decimal]$d = $ts.TotalMinutes '{0:f3}m' -f ($d) | Write-Host -ForegroundColor Gray -NoNewline -BackgroundColor Red break } Default { $_.Milliseconds | Write-Host -ForegroundColor Gray -NoNewline } } Write-Host "]" -NoNewline } } catch { # if there is no history then we dont put anything into the prompt "" } #endregion #region reduce the path displayed if it is long if (($pwd.Path.Split('\').count -gt 2)) { $One = $pwd.path.split('\')[-1] $Two = $pwd.path.split('\')[-2] if ($One.Length -gt 10) { $One = "$($One[0..7] -join '')~" } if ($Two.Length -gt 10) { $Two = "$($Two[0..7] -join '')~" } write-host " $($pwd.path.split('\')[0], '...', $Two, $One -join ('\'))" -NoNewline } else { Write-Host " $($pwd.path)" -NoNewline } #endregion Write-Host "> " -NoNewline } function Write-PSPLog { <# .Synopsis Worker function for PSPrompt .Description Called internally by PSPrompt to store log info and facilitate prompt information such as battery drain rate .Example Write-PSPLog -message ("It's test # $_ everybody." ) -Source "Testing" This writes a message to the PSPLog.log #> [cmdletbinding()] param( # message to be written to the log [parameter()] [string]$Message, # where the message has been sent from [Parameter()] [string] $Source ) begin { $LogFile = "C:\temp\PSPLog.log" # check we have a logfile if (!(test-path $LogFile)) { $null = New-Item -Path $LogFile -ItemType File } else { # check the logfile isnt getting too big $file = get-item $LogFile if ($file.length -gt 5MB) { # if its big rename it and recreate it $date = (get-date).ToString("yyyyMMdd") try { $archive = $($file.FullName -replace '\.log', "") + $date + "_archive.log" $null = $File.CopyTo($archive) Set-Content -Value "" -Path $LogFile $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), 'Internal', "previous PSPrompt Log file archived" $msg | Out-File -FilePath $LogFile -Append } catch { Write-Warning "Unable to archive / reset PSPrompt Log file" $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, "Unable to archive / reset PSPrompt Log file" $msg | Out-File -FilePath $LogFile -Append $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, $error[0].Exception $msg | Out-File -FilePath $LogFile -Append -Encoding } } } } process { $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, $message $msg | Out-File -FilePath $LogFile -Append } end { } } function ConvertFrom-Byte { <# .SYNOPSIS Convert a Byte value to a KB, MB, GB, or TB value .DESCRIPTION Convert a Byte value to a KB, MB, GB, or TB value .PARAMETER Bytes The value in bytes that you want to see expressed in a higher metric .EXAMPLE ConvertFrom-Byte 1234342562 result: 1.15GB .EXAMPLE ConvertFrom-Byte 123434 result: 120.54KB #> [outputtype([system.string])] param ( [parameter(ValueFromPipeline=$true)] [Alias('Length')] [ValidateNotNullorEmpty()] $Bytes ) begin {} process { switch -Regex ([math]::truncate([math]::log([System.Convert]::ToInt64($Bytes), 1024))) { '^0' { "$Total Bytes" ; Break } '^1' { "{0:n2} KB" -f ($Bytes / 1KB) ; Break } '^2' { "{0:n2} MB" -f ($Bytes / 1MB) ; Break } '^3' { "{0:n2} GB" -f ($Bytes / 1GB) ; Break } '^4' { "{0:n2} TB" -f ($Bytes / 1TB) ; Break } '^5' { "{0:n2} PB" -f ($Bytes / 1PB) ; Break } # When we fail to have any matches, 0 Bytes is more clear than 0.00 PB (since <5GB would be 0.00 PB still) Default { "0 Bytes" } } } end {} } function ConvertFrom-Epoch { <# .SYNOPSIS Converts a unix style epoch time to a datetime value .DESCRIPTION Converts a unix style time value like 1559684667 and returns a datetime value like 04 June 2019 21:44:27 .PARAMETER Seconds The integer value that should be used to convert to a date time value .EXAMPLE ConvertFrom-Epoch 1428665400 This returns the date time value of 10 April 2015 12:30:00 #> [outputtype([system.datetime])] [cmdletbinding()] param( #Seconds value since 01-Jan-1970 [parameter(ValueFromPipeline = $true, Mandatory = $true)] $Seconds ) [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($Seconds)) } function ConvertTo-Epoch { <# .SYNOPSIS Converts a datetime to epoch int value .DESCRIPTION Converts a value like 10-Apr-2018 10:45:01 to an epoch style int value .PARAMETER date The date value that should be converted .EXAMPLE ConvertTo-Epoch '10-Apr-2015 12:30:00' This returns the epoch value of 1428665400 #> [outputtype([system.int32])] [cmdletbinding()] param( # date value to convert to epoch [parameter(ValueFromPipeline = $true, Mandatory = $true)][datetime] $date ) [int][double]::Parse((Get-Date ($date).ToUniversalTime() -UFormat %s)) } function Find-Script { <# .SYNOPSIS script finder .DESCRIPTION reviews locations known to have script files in them for specified string .EXAMPLE Find-Script -Search event -Type ps1 Example searches for the string 'event' in filenames with extension matching ps1 .EXAMPLE Find-Script -Search audit -Type sql -includecontent Example searches for the string 'audit' in file names and content with extension matching sql #> [CmdletBinding()] param ( # The string you want to search for [parameter(Mandatory = $false)]$Search, # The file type you want to search for [parameter(Mandatory = $false)]$Type, # Do you want to search in file content too [parameter(Mandatory = $false)][switch]$IncludeContent ) $Type = '*.' + $Type $List = Import-Csv 'C:\Users\jonallen\OneDrive\PowerShellLocations.csv' # $Results = [[PSCustomObject]@ { # MatchType = $null # FileName = $null # FilePath = $null # }] $Results = ForEach ($Item in $List) { ".. Checking $($item.name) .." foreach ($doc in Get-ChildItem $item.Folder -Recurse -include $Type) { if ($doc -match $search) { [pscustomobject]@{ MatchType = "FileName" FileName = $($doc.Name) FilePath = $($doc.FullName) } } else { if ($IncludeContent) { try { $content = Get-Content $doc if ($content -match $Search) { [pscustomobject]@{ MatchType = "Content" FileName = $($doc.Name) FilePath = $($doc.FullName) } } } catch { write-verbose "unable to open $doc" } } } } } return $Results } function Get-BatteryStatus { <# .SYNOPSIS Displays battery status to console .DESCRIPTION Displays battery status to console .EXAMPLE Get-BatteryStatus Get the battery status .EXAMPLE battery uses the alias to call this function #> [outputtype([system.string])] [alias('battery')] [CmdletBinding()] param ( ) process { $b = (Get-CimInstance -ClassName CIM_Battery) $Battery = [PSCustomObject]@{ IsCharging = if ($b.BatteryStatus -eq 1) { "Not Charging" } else { "Charging" } Charge = $b.EstimatedChargeRemaining #.GetValue(1) Remaining = $b.EstimatedRunTime #.GetValue(1) } $msg = "$($Battery.Charge)%" if ($Battery.IsCharging -eq "Charging") { $msg += " $($Battery.IsCharging) " } else { $msg += "/ $($Battery.Remaining) mins - Discharging" } $msg } } Function Get-OutlookCalendar { <# .Synopsis This function returns InBox items from default Outlook profile .Description This function returns InBox items from default Outlook profile. It uses the Outlook interop assembly to use the olFolderInBox enumeration. It creates a custom object consisting of Subject, ReceivedTime, Importance, SenderName for each InBox item. *** Important *** depending on the size of your InBox items this function may take several minutes to gather your InBox items. If you anticipate doing multiple analysis of the data, you should consider storing the results into a variable, and using that. .Example Get-OutlookCalendar -StartTime (get-date).date -EndTime ((get-date).adddays(+7)).date This returns all calendar events for the forth-coming 7 days .Example Get-OutlookCalendar -StartTime (get-date).date -EndTime ((get-date).adddays(+1)).date -verbose This returns all calendar events for the forth-coming 24 hours .Example Get-OutlookCalendar -Today | ft -a -Wrap This example uses the -Today switch to get information just for the current day. Output is formatted as a table .Notes # genesis of Outlook access from https://gallery.technet.microsoft.com/scriptcenter/af63364d-8b04-473f-9a98-b5ab37e6b024 NAME: Get-OutlookInbox AUTHOR: ed wilson, msft LASTEDIT: 05/13/2011 08:36:42 KEYWORDS: Microsoft Outlook, Office HSG: HSG-05-26-2011 .Link Http://www.ScriptingGuys.com/blog #> [outputtype([system.string])] [alias('cal', 'event')] [cmdletbinding(DefaultParameterSetName = "Today")] param( # Start of time span to show calendar events. [Parameter(ParameterSetName = "StartEnd", ValueFromPipelineByPropertyName = $true, HelpMessage = "Start of time span to show calendar events.")] [datetime]$StartTime, # End of time span to show calendar events. [Parameter(ParameterSetName = "StartEnd", ValueFromPipelineByPropertyName = $true, HelpMessage = "End of time span to show calendar events.")] [datetime]$EndTime, # Show calendar events for just today. [Parameter(ParameterSetName = "Today", ValueFromPipelineByPropertyName = $true, HelpMessage = "Show calendar events for just today.")] [switch]$Today, # Show calendar events for next 7 days. [Parameter(ParameterSetName = "Next7Days", ValueFromPipelineByPropertyName = $true, HelpMessage = "Show calendar events for next 7 days.")] # use Next7 to get the next 7 days of calendar events [switch]$Next7 ) begin { Write-Verbose "command is : $command" Write-Verbose " folder items : $(($script:eventsfolder).count) " $null = Add-type -assembly "Microsoft.Office.Interop.Outlook" $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type] $outlook = new-object -comobject outlook.application $namespace = $outlook.GetNameSpace("MAPI") $script:eventsfolder = $namespace.getDefaultFolder($olFolders::olFolderCalendar) $msg = "Getting you outlook calendar takes a few seconds ..." Write-Host $msg $today = $true # just todays events if ($Today) { $StartTime = (get-date).Date $EndTime = ((get-date).AddDays(+1)).date } # events for the whole week if ($Next7) { $StartTime = (get-date).Date $EndTime = ((get-date).AddDays(+7)).date } $BusyStatus = @{ 0 = "Free" 1 = "Tentative" 2 = "Busy" 3 = "Out of Office" 4 = "Working away" } } process { # actually go and get the calendar events for the chosen period $cal = $script:eventsfolder.items | Where-Object { $_.start -gt $StartTime -and $_.start -lt $EndTime } | Select-Object subject, start, end, busystatus, @{name = 'Duration'; expression = { "*" * (New-TimeSpan -Start $_.start -End $_.end).TotalHours } } if ($cal.count -eq 0) { "Nothing in your calendar" } else { $cal | Select-Object Subject, Start, End, @{name = "Busy status"; expression = { $BusyStatus[$_.busystatus] } }, Duration } } end { } } #end function function Invoke-WebSearch { <# .SYNOPSIS Quick search the internet for content .DESCRIPTION Access your preferred search engine directly from your powershell console .PARAMETER search provide the search string and the search engine .EXAMPLE New-WebSsearch -engine google -search 'kitten gifs' Searches google for gifs of kittens .NOTES n/a #> [alias('Google','DuckDuckGo','DDG','Ask','Bing')] [cmdletbinding()] param( # the search string [parameter(ValueFromRemainingArguments=$true)][string]$search ) # begin { if ($search -eq 'gal') { $url = 'https://www.powershellgallery.com/' } # } # process { $engine = (Get-PSCallStack).InvocationInfo.MyCommand.Definition[1] # check how we called the function switch -Regex ($engine) { { $_ -match ('DDG | DuckDuckGo') } { $url = "https://duckduckgo.com/?q=$search"; break } { $_ -match 'Google' } { $url = "https://www.google.co.uk/search?q=$Search"; break } { $_ -match 'Bing' } { $url = "https://www.bing.com/search?q=$Search"; break } { $_ -match 'Ask' } { $url = "https://uk.ask.com/web?q=$Search"; break } default { $url = "https://www.bing.com/search?q=$Search" } } Start-Process $url # } } function New-OutlookCalendar { <# .synopsis command line Outlook calendar event creation .description Quickly and easily add meetings and reminders to your Outlook calendar via your keyboard without leaving your PowerShell host .parameter WhatIf [<SwitchParameter>] If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .parameter Confirm [<SwitchParameter>] If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .example New-OutlookCalendar -Start '20-Mar-2019 12:00' -Subject "Get birthday present for Timmy" -Status 'Out of Office' This example create a 30 minute event in the calendar at 12:00 on 20th Mar 2019 with a subject of "Get birthday present for Timmy" with a reminder .example New-OutlookCalendar -Start (get-date -date ((get-date).ToShortDateString())).AddHours(18) -Subject "After work drinks" -Status 'Busy' -duration 180 This example create a 3 hour event in the calendar at 18:00 today with a subject of "After work drinks" with a reminder .link olItems <https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.outlook.olitemtype?view=outlook-pia> olBusyStatus https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.outlook.olbusystatus?view=outlook-pia #> [cmdletbinding(SupportsShouldProcess = $true)] param( # Start time of new event [Parameter(Mandatory = $true)] [datetime]$Start, # Subject of the new event [Parameter(Mandatory = $true)] [string]$Subject, # How long is the event in minutes (default - 30m) [Parameter()] [int]$Duration = 30, # Busy status (default - Busy) [Parameter()] [validateset('Free', 'Tentative', 'Busy', 'Out of Office', 'Working away')] [string]$Status = "Busy", # No reminder for the event [Parameter()] [switch]$NoReminder ) begin { $null = Add-type -assembly "Microsoft.Office.Interop.Outlook" # unused variable ? $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type] $outlook = new-object -comobject outlook.application # unused variable ? $namespace = $outlook.GetNameSpace("MAPI") switch ($status) { 'Free' { $olStatus = 0 } 'Tentative' { $olStatus = 1 } 'Busy' { $olStatus = 2 } 'Out of Office' { $olStatus = 3 } 'Working away' { $olStatus = 4 } } } process { #region Create New Calendar Item $NewEvent = $Outlook.CreateItem(1) $NewEvent.Subject = $Subject $NewEvent.Start = $Start $NewEvent.duration = $Duration $NewEvent.BusyStatus = $olStatus if ($NoReminder) { $NewEvent.ReminderSet = $false } if ($PSCmdlet.ShouldProcess("Calendar", "Creating new event $($newevent.subject)")) { $NewEvent.save() } #endregion } end { } } function New-ToDo { <# .SYNOPSIS Creates quick To Do list in Notepad .DESCRIPTION Creates quick To Do list in Notepad .PARAMETER List semi-colon separated list of items to put in to do list .parameter WhatIf [<SwitchParameter>] If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .parameter Confirm [<SwitchParameter>] If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .EXAMPLE New-ToDo Basic execution of this function to start a new ToDo list .EXAMPLE New-ToDo -List "Write todo function", "Update calendar" , "Book travel", "submit expenses claim", "cook dinner" Advanced execution of this function to start a new ToDo list with specific items in the list already .NOTES General notes #> [cmdletbinding(SupportsShouldProcess = $true)] param( # semi-colon separated list of items to put in to do list [parameter(ValueFromRemainingArguments = $True)] [string[]]$List ) # split out the items we have been sent $items = $List -split (';') # pad the ToDo items to 5 items with empty lines if ($items.count -lt 5) { $items.count..5 | ForEach-Object { $items += "" } } # set up header of list $txt = @" To do list - {0:dd MMM yyyy}`r`n "@ -f (get-date) # add the items to the doc foreach ($Item in $items) { $txt += @" []`t$Item`r`n "@ } # add the footer (Done) section $txt += @" `r`n** Done **`r`n "@ # create the file and display if ($PSCmdlet.ShouldProcess("new ToDo list file " , "Creating")) { $file = New-TemporaryFile $txt | set-content $file notepad $file } } function Push-PSPrompt { <# .synopsis Worker function that builds up the MyPrompt.ps1 file and dot sources it to apply selecgted changes .description This is the function that actually applies the change to the users session .example Push-PSPrompt Use Push-PSPrompt to make the customisation take effect #> #region build up script from components New-Variable -Name WorkingFolder -Value "$env:APPDATA\PSPrompt" -Option Constant $PromptFile = "$WorkingFolder\MyPrompt.ps1" ## unused variable? $ModulePath = ($env:PSModulePath -split (';'))[1] $mpath = (Get-Module -name psprompt)[-1].path $Path = Split-Path $mpath -parent $child = "\functions\components" Write-Verbose $mpath Write-Verbose $path Write-Verbose $child $components = (Join-Path -path $Path -ChildPath $child) Write-Debug "" # used as a stop line for review of variable assignment during debug $components = (Join-Path -path $Path -ChildPath $child -Resolve) # step one - the boiler-plate start of a function get-content "$components\_header.txt" | Out-File $PromptFile -Force # next read in the settings from the config file created in Set-PSPrompt if (!(test-path "$WorkingFolder\PSPrompt.config" )) { $msg = "Unable to read config file at $WorkingFolder\PSPrompt.config, check that it exists or run Set-PSPrompt. " Write-Warning $msg return } else { Write-Verbose "Reading settings from $WorkingFolder\PSPrompt.config" $PSPromptData = Import-Clixml -Path "$WorkingFolder\PSPrompt.config" } # now for each value from our hash table where True means we need to gather the script component to build up the prompt #region first to build is the 'second' prompt line that is shown occasionally above the prompt If ($PSPromptData.SecondLine) { # add header of Nth command get-content "$components\NthCommandHeader.txt" | Out-File $PromptFile -Force -Append # add second line content switch ($PSPromptData) { { $_.GitStatus } { get-content "$components\GitStatus.txt" | Out-File $PromptFile -Append } } # add footer of Nth command get-content "$components\NthCommandFooter.txt" | Out-File $PromptFile -Force -Append } #endregion #region - now, all the components selected to be shown in the permanent prompt line switch ($PSPromptData) { { $_.Admin } { get-content "$components\admin.txt" | Out-File $PromptFile -Append } { $_.Battery } { get-content "$components\battery.txt" | Out-File $PromptFile -Append } { $_.Day_and_date } { get-content "$components\daydate.txt" | Out-File $PromptFile -Append } { $_.UTC_offset } { get-content "$components\UTC_offset.txt" | Out-File $PromptFile -Append } { $_.last_command } { get-content "$components\last_command.txt" | Out-File $PromptFile -Append } { $_.shortpath } { get-content "$components\shortpath.txt" | Out-File $PromptFile -Append } } #endregion # complete the Prompt function boiler plate in the file so that we can dot source it dreckly get-content "$components\_footer.txt" | Out-File $PromptFile -Append write-verbose "Function compiled from components and now saved as $PromptFile" #region Final step is now to apply the prompt to the current session # dot source the prompt function to apply the changes to the prompt # and then add prompt function code to the profile try { Write-Verbose "Dot sourcing $Promptfile" . $PromptFile Write-Verbose "Adding prompt to CurrentUserAllHosts profile" $prompt = get-content $Promptfile $profilefile = $profile.CurrentUserAllHosts # check if there is a PSPROMPT function already there $Exists = get-content $profilefile if($Exists -match "PSPROMPTSTART(?s)(.*)PSPROMPTEND","jumboreplace" ){ Write-Verbose "Existing prompt found in profile" $Exists -replace "PSPROMPTSTART(?s)(.*)PSPROMPTEND",$null | Set-Content $profilefile Write-Verbose "Previous PSPrompt found and removed from profile" } $prompt | Out-File $profilefile -Append write-host "`r`nCongratulations!! `r`nYour prompt and your CurrentUserAllHosts profile have been updated . If you want to change the components in effect, just run Set-PSPrompt again. `r`nIf you want to remove the PSPrompt changes run Set-PSPrompt -reset`r`n" } catch { Write-Warning "Something went wrong with applying the PSPrompt changes." Write-Warning "Try running <. $PromptFile>" } #endregion } function Set-DisplayBrightness { <# .Synopsis custom function to set screen brightness .Description custom function to set screen brightness .parameter WhatIf [<SwitchParameter>] If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .parameter Confirm [<SwitchParameter>] If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .Example Set-DisplayBrightness 50 This example sets the display brightness to 50% .Example Set-DisplayBrightness -report This example shows the current brightness value in percent .Example Dim 75 This example sets the brightness to 75 using the function alias #> [alias('dim')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWMICmdlet", "")] [cmdletbinding(SupportsShouldProcess = $true)] param(# supply the desired brightness as an integer from 1 to 100 [parameter()] [validaterange(1, 100)] [int]$Brightness = 75, # use this parameter to get the current brightness [parameter()][switch]$Report ) if ($Report) { (Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness).CurrentBrightness } else { $display = Get-WmiObject -Namespace root\wmi -Class WmiMonitorBrightnessMethods # $display = Get-CimInstance -Namespace root\wmi -Class WmiMonitorBrightnessMethods # pscore requires cim if ($PSCmdlet.ShouldProcess("Display", "Setting brightness to $Brightness")) { $display.WmiSetBrightness(1, $Brightness) } } } function Set-PSPrompt { <# .synopsis The function that creates the desired prompt function. .description It has to build a command out of the component pieces as it will be executed everytime a command is executed and therefore cant make use of any parameterisation .parameter WhatIf [<SwitchParameter>] If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .parameter Confirm [<SwitchParameter>] If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .example Set-PSPrompt This will start the process to configure your custom PowerShell prompt #> [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Custom')] Param( # [parameter(parametersetname = "default")][switch]$AddToProfile, # [parameter(parametersetname = "default")][switch]$Admin, # [parameter(parametersetname = "default")][switch]$Battery, # switch to reset to console prompt back to original state [parameter(ParameterSetName = "Reset") ][switch]$Reset ) $PSPromptData = [ordered]@{ } New-Variable -Name WorkingFolder -Value "$env:APPDATA\PSPrompt" -Option Constant $ConfigFile = "PSPrompt*.config" # TODO:: remove this line to set correct config file # create working folder for module if its not there if (!(Test-Path $WorkingFolder)) { $null = New-Item -Path $WorkingFolder -ItemType Directory } #region preserve the original prompt so that it can be reset if so desired Write-Verbose "Preserving original prompt" $Date = Get-Date -Format 'yyMMdd-HHmmss' $filename = "$WorkingFolder\prompt_$date.ps1" $function:prompt | Out-File -FilePath $filename Write-Verbose "Existing prompt written out to $filename." #endregion #region code route options: # 1 gather user preferences from a static json file if it exists # 2 user overrides the json option at (?) and wants to add/remove features # 3 user wants to revert to their original prompt, prior to installing PSPrompt # 4 user selection of prompt features at first execution #region Option A - Reset to original prompt # need to show options for reset and allow for selection # need to put test for $Reset ahead of other steps to avoid choosing something else before being reset # get-item "$WorkingFolder\prompt_*.ps1" # $OldPrompt = if ($Reset) { $ProfileCUAHPath = $profile.CurrentUserAllHosts # $ProfileCUCHPath = $profile.CurrentUserCurrentHost # unused at the moment - may look at different psprompt in different profiles oneday ##$ProfileCUAH = Get-Content $ProfileCUAHPath -Raw ##$ProfileCUCH = Get-Content $ProfileCUCHPath -Raw if (($ProfileCUAHPath).Length -gt 0) { # code $profile.CurrentUserCurrentHost $p = @() $p = get-content $ProfileCUAHPath if (($profile.CurrentUserAllHosts).Length -gt 0) { ## think this isnt needed here $p = get-content $profile.CurrentUserAllHosts if ($p.IndexOf("##PSPROMPTSTART##")) { Write-Verbose "PSPROMPT content found in CurrentUserAllHosts" } } else { Write-Verbose "CurrentUserAllHosts profile is empty" } # locate start and end of psprompt content $Start = $a.IndexOf('##PSPROMPTSTART##') $End = $a.IndexOf('##PSPROMPTEND##') # identify content in profile before psprompt if ($start -ne 0) { $Before = $a[0..($Start - 1)].Clone() } else { $Before = $null } # identify content in profile after psprompt if ($End -ne ($a.Count - 1)) { $After = $a[($End + 1)..($a.Length)].Clone() } else { $After = $null } # put 'before' and 'after' content into the profile file $Before | Out-File -FilePath ($profile.CurrentUserAllHosts) $After | Out-File -FilePath ($profile.CurrentUserAllHosts) -append } return # no more processing in this script } #endregion Option A #region option 1 - there is config file we can load settings from # load the settings # handle multiple config files $ConfigFiles = get-item (join-path $WorkingFolder $configFile) if ($ConfigFiles.count -gt 1) { $i = 1 foreach ($File in $ConfigFiles) { Write-Host "$i - $File" $i++ } do { $LoadConfig = Read-Host "There are multiple config files - which do you want to implement?" Write-Host "Please select the number of the file you want to import, enter 'configure' to create a new prompt or press Ctrl + C to cancel." } until($LoadConfig -in ((1..$Configfiles.count), 'configure' ) ) ## TODO - need to work out how to go to configure from here. makes me wonder if this should be step 1 ... $PSPromptData = Import-Clixml $configFiles[$LoadConfig - 1] } elseif ($ConfigFiles.count -lt 1) { Write-Warning "No config files found. Need to do something here." } else { $PSPromptData = Import-Clixml $configFiles # confirm to user Write-Host "There is a config file that will enable the following PSPrompt features:`r`n" $PSPromptData while ($choice -notin ('yes', 'no')) { $choice = Read-Host -Prompt "Do you want to apply these settings? (Yes) or (No)." } if ($choice -eq 'yes') { if ($PSCmdlet.ShouldProcess("Console Prompt", "Customising being applied")) { Push-PSPrompt $PSPromptData } } elseif ($choice -eq 'no') { Write-Verbose "Removing PSPrompt config file." Remove-Item $ConfigFiles Write-Verbose "Removing MyPrompt.ps1 config file." Remove-Item -path (join-path -path (split-path -path $ConfigFiles -parent) -child "myprompt.ps1") Write-Verbose "PSPrompt config file removed." return } } Write-Verbose "Loading from (join-path $WorkingFolder $configFiles[$LoadConfig - 1])" $msg = ("WorkingFolder: {0}; ConfigFiles: {1}" -f $WorkingFolder, $ConfigFiles) Write-Verbose $msg # confirm to user #Write-Host "There a config file that will enable the following PSPrompt features:`r`n" #Write-Output $PSPromptData # #$msg = "Do you want to (e)nable these features or (c)onfigure different ones?" # #do { # $r = Read-Host -Prompt $msg #}while ($r -notin ('e', 'c')) # #if ($r -eq 'e') { # write-host "Loading custom fprompt from $Configfiles" # Push-PSPrompt #} #else { # Remove-Item $configFiles #} # TODO:: need to add check its OK - then move to (currently) line 230 ##Push-PSPrompt $PSPromptData #endregion option 1 #region option 2 - custom choice from command line #endregion option 2 # temporary check of file contents during development # start-process notepad "$WorkingFolder\PSPrompt.config" # hand over to function that reads in function sectors based on config file settings and invokes the prompt function #*#*#*# Push-PSPrompt $PSPromptData #region option 3 - reset #endregion option 3 #region option 4 That means we are at first usage of Set-Prompt so we need to collect user preference if (!(test-path (join-path $WorkingFolder $ConfigFile))) { Clear-Host $msg = @() $msg += "Welcome to PSPrompt. It looks like this is your first time using the prompt so you need to make some choices on how you want your prompt to look." $msg += "There are some choices coming up and you need to respond with (Y)es or (N)o to each. When you respond (Yes) that feature will be included in your prompt." $msg += "If you respond (N)o then the feature wont be applied." $msg += "If you want to change your mind at any time, run the command Set-Prompt -options and you will enter this menu again." $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } #region Admin user $msg = $null $msg += "`r`n`r`nFirst of all, do you want a notification when you are running a PS session as an Administrator level account?" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } Write-Host -ForegroundColor "Black" -BackgroundColor "DarkRed" "[ADMIN]" $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.Adminuser = $true } else { $PSPromptData.Adminuser = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region Battery Clear-Host $msg = $null $msg += "`r`n`r`nNext, do you work from a laptop? Do you want an indicator of your battery status while you are working on battery power?" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } $msg = ("[75%:60m]" ) Write-Host -Object $msg -BackgroundColor Green -ForegroundColor Black $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.Battery = $true } else { $PSPromptData.Battery = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region Day and Date Clear-Host $msg = $null $msg += "`r`n`r`nNeed to keep your eye on the time? Add the day/date to your prompt?" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } $msg = "[{0}]" -f (Get-Date -Format "ddd HH:mm:ss") Write-Host $msg $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.Day_and_date = $true } else { $PSPromptData.Day_and_date = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region UTC offset Clear-Host $msg = $null $msg += "`r`n`r`nIf you work across timezones then as your laptop updates to a different timezone we can have your timezone offset indicated in your prompt.`r`n Add the timezone offset to your prompt?" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } Write-Host "`r`n(GMT +1)" -ForegroundColor Green -NoNewline Write-Host "`tor`t" -ForegroundColor white -NoNewline Write-Host "(GMT -3) `r`n" -ForegroundColor Red $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.UTC_offset = $true } else { $PSPromptData.UTC_offset = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region Fun function #endregion #region last command duration Clear-Host $msg = $null $msg += "`r`n`r`nEveryone likes to write fast executing scripts. Have the execution time of your last script shown right where you are focussed.`r`n Add the previous script duration to your prompt?" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } Write-Host "`r`n[56ms]" -ForegroundColor Green -NoNewline Write-Host "`tor`t" -ForegroundColor white -NoNewline Write-Host "[29s] `r`n" -ForegroundColor Red $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.last_command = $true } else { $PSPromptData.last_command = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region Short Path Clear-Host $msg = $null $msg += "`r`n`r`nSometimes you get down a lot of folder levels and the prompt gets really wide.`r`n We can give you a shortened version of the path" $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } Write-Host "`r`nC:\...\PowerShell\ProfileF~>" -NoNewline Write-Host "`tinstead of `t" -ForegroundColor white -NoNewline Write-Host "C:\Users\jonallen\OneDrive\Scripts\PowerShell\ProfileFunctions `r`n" $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.shortpath = $true } else { $PSPromptData.shortpath = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region second line every Nth command Clear-Host $msg = $null $msg += @" There are some things that its good to keep your eye on but you don't need to see every time you run a command. So, we can show you some information every few commands (default is every 5 commands). Would you like a second line to occasionally give you info about your Git status or perhaps additional battery information? "@ $msg += "`r`nLike this:" $msg | ForEach-Object { Write-Host $_ start-sleep -Milliseconds 800 } Write-Host "git status: " -ForegroundColor White -BackgroundColor Black -NoNewline Write-Host "New[0] " -ForegroundColor Green -NoNewline Write-Host "Mod[7] " -ForegroundColor Cyan -NoNewline Write-Host "Del[1]" -ForegroundColor Red $r = read-host -Prompt "(Y)es to have this occasionally in your prompt, (N)o to give it a miss." if ($r -eq "y") { $PSPromptData.SecondLine = $true $PSPromptData.GitStatus = $true } else { $PSPromptData.GitStatus = $false } # record selection into xml file $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config" #endregion #region Battery decline rate # still not sure on this so leaving code in Nth command.ps1 - JA 20190503 #endregion } #endregion (option 4) #endregion all options } function Show-Calendar { <# .Synopsis Displays a visual representation of a calendar. .Description Displays a visual representation of a calendar. This function supports multiple months and lets you highlight specific date ranges or days. .Parameter Start The first month to display. .Parameter End The last month to display. .Parameter FirstDayOfWeek The day of the month on which the week begins. .Parameter HighlightDay Specific days (numbered) to highlight. Used for date ranges like (25..31). Date ranges are specified by the Windows PowerShell range syntax. These dates are enclosed in square brackets. .Parameter HighlightDate Specific days (named) to highlight. These dates are surrounded by asterisks. .Example # Show a default display of this month. Show-Calendar .Example # Display a date range. Show-Calendar -Start "March, 2010" -End "May, 2010" .Example # Highlight a range of days. Show-Calendar -HighlightDay (1..10 + 22) -HighlightDate "December 25, 2008" .Example # Highlight a range of days. Show-Calendar -Start "Nov, 2010" -End "Dec, 2010" -HighlightDay (1..10 + 22) -HighlightDate "December 25, 2010" .Notes code sample from https://docs.microsoft.com/en-us/powershell/developer/module/how-to-write-a-powershell-script-module #> [cmdletbinding()] param( [DateTime] $start = [DateTime]::Today, [DateTime] $end = $start, $firstDayOfWeek, [int[]] $highlightDay, [string[]] $highlightDate = [DateTime]::Today #.ToString() ) ## Determine the first day of the start and end months. $start = New-Object DateTime $start.Year, $start.Month, 1 $end = New-Object DateTime $end.Year, $end.Month, 1 ## Convert the highlighted dates into real dates. [DateTime[]]$highlightDate = [datetime[]]$highlightDate ## Retrieve the DateTimeFormat information so that the ## calendar can be manipulated. $dateTimeFormat = (Get-Culture).DateTimeFormat if ($firstDayOfWeek) { $dateTimeFormat.FirstDayOfWeek = $firstDayOfWeek } $currentDay = $start ## Process the requested months. while ($start -le $end) { ## Return to an earlier point in the function if the first day of the month ## is in the middle of the week. while ($currentDay.DayOfWeek -ne $dateTimeFormat.FirstDayOfWeek) { $currentDay = $currentDay.AddDays(-1) } ## Prepare to store information about this date range. $currentWeek = New-Object PsObject $dayNames = @() $weeks = @() ## Continue processing dates until the function reaches the end of the month. ## The function continues until the week is completed with ## days from the next month. while (($currentDay -lt $start.AddMonths(1)) -or ($currentDay.DayOfWeek -ne $dateTimeFormat.FirstDayOfWeek)) { ## Determine the day names to use to label the columns. $dayName = "{0:ddd}" -f $currentDay if ($dayNames -notcontains $dayName) { $dayNames += $dayName } ## Pad the day number for display, highlighting if necessary. $displayDay = " {0:00} " -f $currentDay.Day ## Determine whether to highlight a specific date. if ($highlightDate) { $compareDate = New-Object DateTime $currentDay.Year, $currentDay.Month, $currentDay.Day if ($highlightDate -contains $compareDate) { $displayDay = "*" + ("{0:00}" -f $currentDay.Day) + "*" } } ## Otherwise, highlight as part of a date range. if ($highlightDay -and ($highlightDay[0] -eq $currentDay.Day)) { $displayDay = "[" + ("{0:00}" -f $currentDay.Day) + "]" $null, $highlightDay = $highlightDay } ## Add the day of the week and the day of the month as note properties. $currentWeek | Add-Member NoteProperty $dayName $displayDay ## Move to the next day of the month. $currentDay = $currentDay.AddDays(1) ## If the function reaches the next week, store the current week ## in the week list and continue. if ($currentDay.DayOfWeek -eq $dateTimeFormat.FirstDayOfWeek) { $weeks += $currentWeek $currentWeek = New-Object PsObject } } ## Format the weeks as a table. $calendar = $weeks | Format-Table $dayNames -AutoSize | Out-String ## Add a centered header. $width = ($calendar.Split("`n") | Measure-Object -Maximum Length).Maximum $header = "{0:MMMM yyyy}" -f $start $padding = " " * (($width - $header.Length) / 2) $displayCalendar = " `n" + $padding + $header + "`n " + $calendar $displayCalendar.TrimEnd() ## Move to the next month. $start = $start.AddMonths(1) } } #Export-ModuleMember -Function Show-Calendar #endregion Load compiled code |