Functions/Public/Utilities.ps1
|
# Exported utility functions (charts, lists, conversions) Function Convert-NectarNumToTelURI { <# .SYNOPSIS Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .DESCRIPTION Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .PARAMETER PhoneNumber The phone number to convert to a TEL URI .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Convert-NectarNumToTelsURI "+12224243344 x3344" Converts the above number to a TEL URI .EXAMPLE Get-NectarUnallocatedNumber -LocationName Jericho | Convert-NectarNumToTelURI Returns the next available phone number in the Jericho location in Tel URI format .NOTES Version 1.1 #> Param ( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName, Mandatory=$true)] [Alias("number")] [string]$PhoneNumber ) $PhoneNumber = "tel:" + $PhoneNumber.Replace(" x", ";ext=") Return $PhoneNumber } Function Get-LatLong { <# .SYNOPSIS Returns the geographical coordinates for an address. .DESCRIPTION Returns the geographical coordinates for an address. .PARAMETER Address The address of the location to return information on. Include as much detail as possible. .EXAMPLE Get-LatLong -Address "33 Main Street, Jonestown, NY, USA" Retrieves the latitude/longitude for the selected location .NOTES Version 1.0 #> Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$Address ) Begin { $GoogleGeoAPIKey = [System.Environment]::GetEnvironmentVariable('GoogleGeocode_API_Key','user') $GeoapifyAPIKey = [System.Environment]::GetEnvironmentVariable('Geoapify_API_Key','user') If ($GeoapifyAPIKey) { $GeoProvider = 'Geoapify' $GeoAPIKey = $GeoapifyAPIKey } If ($GoogleGeoAPIKey) { $GeoProvider = 'Google' $GeoAPIKey = $GoogleGeoAPIKey } If(!$GeoProvider) { Write-Host -ForegroundColor Red 'No valid GeoProvider found. You need to register for an API key from either Google or Geoapify and save it as persistent environment variable called GoogleGeocode_API_Key or Geoapify_API_Key on this machine.' Write-Host Write-Host -ForegroundColor Red 'For a Google API Key (requires payment) - https://developers.google.com/maps/documentation/geocoding/get-api-key' Write-Host -ForegroundColor Red 'For a Geoapify API Key (free account allows 3000 lookups/day) - https://www.geoapify.com/api/geocoding-api/' Write-Host Write-Host -ForegroundColor Red 'Once obtained, save the API key as a persistent environment variable using one of the following commands:' Write-Host -ForegroundColor Red " [System.Environment]::SetEnvironmentVariable('GoogleGeocode_API_Key', 'YourAPIKey',[System.EnvironmentVariableTarget]::User)" Write-Host -ForegroundColor Red " [System.Environment]::SetEnvironmentVariable('Geoapify_API_Key', 'YourAPIKey',[System.EnvironmentVariableTarget]::User)" Break } } Process { Switch ($GeoProvider) { 'Geoapify' { $URI = "https://api.geoapify.com/v1/geocode/search?text=$Address&apiKey=$GeoAPIKey" Break } 'Google' { $URI = "https://maps.googleapis.com/maps/api/geocode/json?address=$Address&key=$GeoAPIKey" Break } } Try { Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -Uri $URI -ErrorVariable EV Switch ($GeoProvider) { 'Geoapify' { [double]$Lat = $JSON.features.geometry.coordinates[1] [double]$Lng = $JSON.features.geometry.coordinates[0] Break } 'Google' { [double]$Lat = $JSON.results.geometry.location.lat [double]$Lng = $JSON.results.geometry.location.lng Break } } If ($Lat) { $LatLong = New-Object PSObject $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value $Lat $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value $Lng } Else { Write-Host -ForegroundColor Yellow "WARNING: Address geolocation failed for $Address. Defaulting to 0:0" $LatLong = New-Object PSObject $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value 0 $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value 0 } } Catch { "Something went wrong. Please try again." $ev.message } Return $LatLong } } Function Show-GroupAndStats { <# .SYNOPSIS Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .DESCRIPTION Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .PARAMETER InputObject The data to group and sum. Can be pipelined .PARAMETER GroupBy The parameter to group on .PARAMETER SumBy The parameter to calculate numerical statistics. Must be a numeric field .PARAMETER ShowGroupMembers The field to show the members of the field used in the current grouping .PARAMETER ShowSumByAsTimeFormat If the SumBy parameter is in seconds (Duration is an example), format the output as dd.hh:mm:ss instead of seconds .EXAMPLE Get-NectarSession | Show-GroupAndStats -GroupBy CallerLocation -SumBy Duration Will group all calls by caller location and show sum, avg, min, max for the Duration column .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$True)] [Alias('Input')] [pscustomobject[]]$InputObject, [Parameter(Mandatory=$True)] [string[]]$GroupBy, [Parameter(Mandatory=$True)] [string]$SumBy, [Parameter(Mandatory=$False)] [string]$ShowGroupMembers, [switch]$ShowSumByAsTimeFormat ) # Validate the parameters exist and are the proper format ForEach ($Item in $GroupBy) { If ($NULL -eq ($InputObject | Get-Member $Item)) { Write-Error "$Item is not a valid parameter for the source data" Break } } If ($NULL -eq ($InputObject | Get-Member $SumBy)) { Write-Error "$SumBy is not a valid parameter for the source data" Break } # ElseIf (($InputObject | Get-Member $SumBy).Definition -NotMatch 'int|float|double|decimal') { # Write-Error "$SumBy is not a numeric field" # Break # } # First, use the standard Group-Object to do the grouping. [System.Collections.ArrayList]$Output = @() $InputObject | Group-Object $GroupBy | Sort-Object Count -Descending | ForEach-Object { $RowData = [pscustomobject][ordered] @{} Write-Verbose "Name: $($_.Name)" # If grouping by multiple fields, the 'name' field consists of each of the grouped items separated by commas. # This function splits them apart into their own column ForEach ($Item in $GroupBy) { $ItemName = ($_.Name.Split(',')[$GroupBy.IndexOf($Item)]) If (!$ItemName) { $ItemName = 'Unknown'} # Don't allow for blanks. $RowData | Add-Member -MemberType NoteProperty -Name $Item -Value $ItemName.Trim() } $RowData | Add-Member -MemberType NoteProperty -Name 'Count' -Value $_.Count If ($ShowSumByAsTimeFormat) { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Sum).Sum)) $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([timespan]::FromSeconds([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average))) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Minimum).Minimum)) $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Maximum).Maximum)) } Else { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Sum).Sum $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average,2)) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Minimum).Minimum $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Maximum).Maximum } If ($ShowGroupMembers) { $GroupMemberList = $NULL $_.Group.$ShowGroupMembers | ForEach-Object { $GroupMemberList += ($(If($GroupMemberList){','}) + $_) } $RowData | Add-Member -MemberType NoteProperty -Name "$($ShowGroupMembers)_List" -Value $GroupMemberList } $Output += $RowData } Return $Output } Function New-Chart { <# .SYNOPSIS Creates a chart PNG file based on the input data .DESCRIPTION Creates a chart PNG file based on the input data. ONLY WORKS IN MS WINDOWS .PARAMETER InputData The data source to use for the chart. Can be either a variable or a command enclosed in brackets .PARAMETER TimeObjectName The name of the data column to use for the time (x-axis) .PARAMETER BarNames The names of the bars to display separated by commas. Must match up with the names of the desired column in the input data .PARAMETER BarColours The colours of the bars to display separated by commas. Colours will be matched up with the BarNames by position. .PARAMETER Interval The interval between numbers to show on the axis. Defaults to auto. .PARAMETER ChartName The name to use for the chart header and the filename. Defaults to the type of chart being generated. .PARAMETER ChartType The chart type to display. Defaults to StackedColumn. .EXAMPLE $Data = Get-NectarSessionCount -TimePeriod LAST WEEK New-Chart -InputData $Data -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Creates a bar chart using a variable from a previous command .EXAMPLE New-Chart -InputData (Get-NectarSessionCount -TimePeriod LAST WEEK) -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Same results as previous example, but shown as full command written within the New-Chart command .NOTES Version 1.0 #> param ( [Parameter(Mandatory=$True)] [PSCustomObject]$InputData, [Parameter(Mandatory=$True)] [string]$TimeObjectName, [Parameter(Mandatory=$True)] [string[]]$BarNames, [Parameter(Mandatory=$False)] [string[]]$BarColours, [Parameter(Mandatory=$False)] [int32]$Interval, [Parameter(Mandatory=$False)] [string]$ChartName, [Parameter(Mandatory=$False)] [ValidateSet('Area', 'Bar', 'BoxPlot', 'Bubble', 'Column', 'Doughnut', 'Line', 'Pie', 'Point', 'Polar', 'Radar', 'Range', 'RangeBar', 'RangeColumn', 'Spline', 'SplineArea', 'SplineRange', 'StackedArea', 'StackedArea100', 'StackedBar', 'StackedBar100', 'StackedColumn', 'StackedColumn100', 'StepLine', IgnoreCase=$True)] [string]$ChartType = 'StackedColumn' ) [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") # Creating chart object # The System.Windows.Forms.DataVisualization.Charting namespace contains methods and properties for the Chart Windows forms control. $ChartObject = New-Object System.Windows.Forms.DataVisualization.Charting.Chart $ChartObject.Width = 2000 $ChartObject.Height = 1000 $ChartObject.BackColor = [System.Drawing.Color]::white # Set Chart title If (!$ChartName) { $ChartName = $ChartType } [void]$ChartObject.Titles.Add("$ChartName for $($InputData[0].TenantName)") $ChartObject.Titles[0].Font = "Arial,13pt" $ChartObject.Titles[0].Alignment = "TopCenter" # Create a chartarea to draw on and add to chart $ChartAreaObject = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $ChartAreaObject.Name = "ChartArea1" $ChartAreaObject.AxisY.Title = "Count" $ChartAreaObject.AxisX.Title = "Date" $ChartAreaObject.AxisY.Interval = $Interval $ChartAreaObject.AxisX.Interval = 1 $ChartAreaObject.BackColor = [System.Drawing.Color]::white $ChartObject.ChartAreas.Add($ChartAreaObject) # Creating legend for the chart $ChartLegend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $ChartLegend.name = "Legend1" $ChartObject.Legends.Add($ChartLegend) ForEach ($Bar in $BarNames) { [void]$ChartObject.Series.Add($Bar) $ChartObject.Series[$Bar].ChartType = $ChartType $ChartObject.Series[$Bar].BorderWidth = 3 $ChartObject.Series[$Bar].IsVisibleInLegend = $true $ChartObject.Series[$Bar].chartarea = "ChartArea1" $ChartObject.Series[$Bar].Legend = "Legend1" If ($BarColours[$BarNames.IndexOf($Bar)]) { $ChartObject.Series[$Bar].color = $BarColours[$BarNames.IndexOf($Bar)] } $InputData | ForEach-Object {$NULL = $ChartObject.Series[$Bar].Points.addxy([datetime]$_.$TimeObjectName, $_.$Bar) } } # Save chart with the Time frame for identifying the usage at the specific time $ChartObject.SaveImage("$($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png","png") Write-Host "Chart saved as $($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png" } # From https://github.com/allynl93/getSAMLResponse-Interactive # Unfortunately, it relies on System.Windows.Forms, which uses IE and doesn't work with most modern IDPs Function Select-FromList { <# .SYNOPSIS Displays a list of items and allows the user to select one. .DESCRIPTION Displays a list of items and allows the user to select one, with optional highlighting and lowlighting of specific items. .PARAMETER Items The list of items to display. .PARAMETER Prompt The prompt to display to the user. .PARAMETER Highlight An array of strings representing the *exact* items to highlight (brighter color). Only items that match *exactly* (case-insensitive) will be highlighted. This can be a variable containing an array. .PARAMETER Lowlight An array of strings representing the *exact* items to lowlight (dimmer color). Only items that match *exactly* (case-insensitive) will be lowlighted. This can be a variable containing an array. Lowlight takes precedence over Highlight. .PARAMETER Default The item to pre-select (highlight) in the list. This takes precedence over both `Highlight` and `Lowlight`. .EXAMPLE Select-FromList -Items @("Item1", "Item2", "Item3") -Prompt "Choose an item:" .EXAMPLE Select-FromList -Items $PSList -Prompt "Choose an item:" -Default "Item2" .EXAMPLE Select-FromList -Items @("apple", "banana", "orange", "grapefruit") -Prompt "Select a fruit:" -Highlight @("orange", "grapefruit") # This will highlight "orange" and "grapefruit". .EXAMPLE $items = 1..10 | ForEach-Object {"Item $_"} Select-FromList -Items $items -Prompt "Select a number:" -Highlight @("Item 1", "Item 5", "Item 10") -Default "Item 3" # Highlights "Item 1", "Item 10", "Item 5". "Item 3" will be selected by default. .EXAMPLE Select-FromList -Items @("File1.txt", "File2.log", "File3.txt", "Backup.zip") -Prompt "Select a file:" -Highlight @("File1.txt", "File3.txt") # Highlights "File1.txt" and "File3.txt". .EXAMPLE $myHighlights = @("Item2", "Item4") Select-FromList -Items @("Item1", "Item2", "Item3", "Item4", "Item5") -Prompt "Select an item:" -Highlight $myHighlights # Highlights "Item2" and "Item4" .EXAMPLE $myHighlights = @("item2", "ITEM4") # mixed case $myLowlights = @("Item3") Select-FromList -Items @("Item1", "Item2", "Item3", "Item4", "Item5") -Prompt "Select:" -Highlight $myHighlights -Lowlight $myLowlights -Default "item3" # Highlights "Item2" and "Item4", Lowlights "Item3", and selects "Item3" by default. .EXAMPLE $myLowlights = @("Item3", "Item1") Select-FromList -Items (1..5 | ForEach-Object {"Item$_"}) -Lowlight $myLowlights #> Param( [Parameter(Mandatory = $true)] [string[]]$Items, [string]$Prompt = "Select an item:", [string[]]$Highlight, [string[]]$Lowlight, [string]$Default ) # Validate if $items is empty If ($Items.Length -eq 0) { Write-Host "Error: List is empty" -ForegroundColor Red Return $null; } $selectedIndex = 0 $itemsCount = $Items.Count # Set the initial index based on Default If ($Default) { For ($i = 0; $i -lt $itemsCount; $i++) { If ($Items[$i] -like "*$($Default)*") { # Use -ieq for case-insensitive comparison $selectedIndex = $i Break } } } While ($true) { Clear-Host Write-Host $Prompt For ($i = 0; $i -lt $itemsCount; $i++) { $line = " " + ($i + 1) + ". " + $Items[$i] $isHighlighted = $false $isLowlighted = $false # Check for lowlight first (takes precedence, *except* when selected) if ($Lowlight) { if ($Items[$i] -in $Lowlight) { $isLowlighted = $true } } # Check for highlight (only if not lowlighted) if ($Highlight -and !$isLowlighted) { if ($Items[$i] -in $Highlight) { $isHighlighted = $true } } # Selected item is *always* green if ($i -eq $selectedIndex) { Write-Host ">>" -NoNewline -ForegroundColor Green Write-Host $line -ForegroundColor Green } elseif ($isLowlighted) { Write-Host " " -NoNewline Write-Host $line -ForegroundColor DarkGray # Just Lowlighted } elseif ($isHighlighted) { Write-Host " " -NoNewline Write-Host $line -ForegroundColor Yellow # Just Highlighted } else { Write-Host " " -NoNewline Write-Host $line # Normal item } } $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") If ($key.VirtualKeyCode -eq 38) { # Up Arrow $selectedIndex = ($selectedIndex - 1 + $itemsCount) % $itemsCount } ElseIf ($key.VirtualKeyCode -eq 40) { # Down Arrow $selectedIndex = ($selectedIndex + 1) % $itemsCount } ElseIf ($key.VirtualKeyCode -eq 13) { # Enter Return $Items[$selectedIndex] Break } ElseIf ($key.VirtualKeyCode -eq 27) { # ESCAPE Return $null Break } } } |