Skip to content

Instantly share code, notes, and snippets.

@Nora-Ballard
Last active August 29, 2015 14:00
Show Gist options
  • Select an option

  • Save Nora-Ballard/11240076 to your computer and use it in GitHub Desktop.

Select an option

Save Nora-Ballard/11240076 to your computer and use it in GitHub Desktop.
Pushover.net Powershell Module for sending notification Messages to your phone. Requires you to generate an Application Token and provide your User Key from the Pushover.net site.
#Requires -Version 3
[CmdletBinding()]
param(
[Parameter()]
[string]$TokenFile = (Join-Path $env:LOCALAPPDATA 'PushoverModule_CachedToken.xml')
)
#region Functions
function ConvertTo-UnixDateTime {
param(
[Parameter(ValueFromPipeline)]
[datetime]$DateTime = [DateTime]::UtcNow
)
$unixEpochStart = new-object DateTime 1970,1,1,0,0,0,([DateTimeKind]::Utc)
[Convert]::ToInt64((New-TimeSpan -Start $unixEpochStart -End $DateTime.ToUniversalTime()).TotalSeconds)
}
function ConvertFrom-UnixDateTime {
param(
[Parameter(Mandatory,ValueFromPipeline)]
[int32]$UnixDateTime
)
$unixEpochStart = New-Object DateTime 1970,1,1,0,0,0,([DateTimeKind]::Utc)
$unixEpochStart.AddSeconds($UnixDateTime).ToLocalTime()
}
function Convert-SecureStringToPlainText {
param(
[Parameter(Mandatory, ValueFromPipeline)]
$InputObject
)
PROCESS
{
$BSTR = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($InputObject)
$String = [Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
Write-Output $String
}
}
function Get-PushoverUser {
param(
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9_-]{25}$'})]
[string]$Device,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$User = $Script:User,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token
)
$SystemWebProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$WebRequest = @{
'Uri' = 'https://api.pushover.net/1/users/validate.json'
'Method' = [System.Net.WebRequestMethods+Http]::Post
}
if (!$SystemWebProxy.IsBypassed($WebRequest.Uri)) {
$Proxy = $SystemWebProxy.GetProxy($WebRequest.Uri)
$WebRequest.Add('Proxy',$Proxy.AbsoluteUri)
$WebRequest.Add('ProxyUseDefaultCredentials',$true)
}
$Body = @{
'token' = $Token
'user' = $User
}
if ($Device) {$Body.Add('device',$Device)}
$RequestResponse = Invoke-RestMethod @WebRequest -Body $Body
Write-Output $RequestResponse
}
Export-ModuleMember -Function Get-PushoverUser
function Get-PushoverSounds {
param(
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token
)
$SystemWebProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$WebRequest = @{
'Uri' = "https://api.pushover.net/1/sounds.json?token={0}" -f $Script:Token
'Method' = [System.Net.WebRequestMethods+Http]::Get
}
if (!$SystemWebProxy.IsBypassed($WebRequest.Uri)) {
$Proxy = $SystemWebProxy.GetProxy($Webrequest.Uri)
$WebRequest.Add('Proxy',$Proxy.AbsoluteUri)
$WebRequest.Add('ProxyUseDefaultCredentials',$true)
}
try {
$RequestResponse = Invoke-WebRequest @WebRequest
$Content = ConvertFrom-Json $RequestResponse.Content
$Sounds = @('none') + $Content.sounds.psobject.properties.Name
}
Catch {
$Sounds = @('none','Pushover')
}
Write-Output $Sounds
}
function Get-PushoverReceipt {
param(
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Receipt
)
PROCESS
{
$SystemWebProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$WebRequest = @{
'Uri' = 'https://api.pushover.net/1/receipts/{0}.json?token={1}' -f $Receipt, $Token
'Method' = [System.Net.WebRequestMethods+Http]::Get
}
if (!$SystemWebProxy.IsBypassed($WebRequest.Uri)) {
$Proxy = $SystemWebProxy.GetProxy($WebRequest.Uri)
$WebRequest.Add('Proxy',$Proxy.AbsoluteUri)
$WebRequest.Add('ProxyUseDefaultCredentials',$true)
}
$Response = Invoke-RestMethod @WebRequest
if ($Response.Status -eq 1) {
$Response.psobject.properties | Where Name -Match '.*_at' | Where Value -ne '0' | ForEach-Object {
$_.Value = ConvertFrom-UnixDateTime $_.Value
}
$Response.psobject.properties | Where Name -NotMatch '.*_(by|at)' | Where Value -in @(0,1) | ForEach-Object {
$_.Value = [bool]$_.Value
}
}
Write-Output $Response
}
}
Export-ModuleMember -Function Get-PushoverReceipt
Function Wait-PushoverAcknowleged {
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Receipt,
[Parameter()]
[switch]$AsJob,
[Parameter()]
[switch]$UntilExpired,
[Parameter()]
[ValidateScript({$_ -ge 5})]
[int32]$RetrySeconds = 30,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token
)
$JobScriptText = @"
function ConvertFrom-UnixDateTime {
${function:ConvertFrom-UnixDateTime}
}
function Get-PushoverReceipt {
${function:Get-PushoverReceipt}
}
function Wait-PushoverAcknowleged {
${function:Wait-PushoverAcknowleged}
}
`$Parameters = `$Using:PSBoundParameters.Clone()
`$Parameters.Remove('AsJob')
`$Parameters.Remove('Token')
Wait-PushoverAcknowleged @Parameters -Token `$Using:Token
"@
if ($AsJob) {
Start-Job -ScriptBlock ([scriptblock]::Create($JobScriptText))
}
else {
do {
$ReceivedReceipt = Get-PushoverReceipt -Receipt $Receipt -Token $Token
if ($ReceivedReceipt.last_delivered_at -ne $last_delivered_at) {
$last_delivered_at = $ReceivedReceipt.last_delivered_at
Write-Warning ("Message Delivered at {0}" -f $last_delivered_at)
}
if ($ReceivedReceipt.expired -and !$MessageExpired) {
$MessageExpired = $true
Write-Warning ("Message Expired at {0}" -f $ReceivedReceipt.Expires_At)
}
if ($ReceivedReceipt.Acknowledged -or ($UntilExpired -and $MessageExpired)) {
Write-Output $ReceivedReceipt
$KeepWaiting = $false
}
else {
Start-Sleep -Seconds $RetrySeconds
$KeepWaiting = $true
}
} While ($KeepWaiting)
}
}
Export-ModuleMember -Function Wait-PushoverAcknowleged
function Send-PushoverMessage {
<#
.SYNOPSIS
This function sends a push notification using Pushover.net
.DESCRIPTION
This function sends a push notification using Pushover.net
.EXAMPLE
Send-PushoverMessage -Message "Test Message"
status request
------ -------
True 97753634235fe120f5c387675a0481dd
Send a simple message to all registered devices.
.EXAMPLE
Send-PushoverMessage -Message "Emergency Message" -Priority Emergency -Retry 60 -Expire 3600
receipt status request
------- ------ -------
XsvgjDqcHHkcFoKAJ5fiKhLcFF27CC True 70f5020156e2e23dc146ca326fee90df
Send an emergency message to all registered devices, and will retry every 60 seconds for 1 hour.
.EXAMPLE
Send-PushoverMessage -Message "Test Message" -Device "TestDevice"
status request
------ -------
True 97753634235fe120f5c387675a0481dd
Send a simple message to the specified registered device.
.EXAMPLE
Send-PushoverMessage -Message "Test Message" -Device $pushoverDevices[0]
status request
------ -------
True 97753634235fe120f5c387675a0481dd
Send a simple message to the specified registered device from the user's device list.
.PARAMETER Message
Your message
.PARAMETER Title
Your message's title, otherwise your app's name is used.
.PARAMETER Priority
The priority of the message; Low is quiet, Normal is the default, High will bypass quiet hours, Emergency requires confirmation from the user.
.PARAMETER Retry
Specifies how often (in seconds) the Pushover servers will send the same notification to the user. Required when the Priority is 'Emergency'.
.PARAMETER Expire
Specifies how many seconds your notification will continue to be retried for. Required when the Priority is 'Emergency'.
.PARAMETER Url
Includes a supplementary URL that is not included in the message text, but available for the user to click on.
.PARAMETER UrlTitle
Title to display for the supplied Url (defaulting to the URL itself if no title given).
.PARAMETER TimeStamp
TimeStamp to show on the the message instead of when it was initially received through the API
.PARAMETER Device
Device name to send the message directly to, rather than all of the user's devices.
.NOTES
Author : Jacob Ballard
.LINK
https://gist.github.com/jakeballard/11240076
#>
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[ValidateLength(1,512)]
[string]$Message,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateLength(1,100)]
[string]$Title,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateSet('Low','Normal','High','Emergency')]
[string]$Priority = 'Normal',
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateLength(1,512)]
[ValidateScript({
[System.Uri]::TryCreate($_, [System.UriKind]::RelativeOrAbsolute, [ref]$_)
})]
[string]$Url,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateLength(1,100)]
[string]$Url_Title,
[Parameter(ValueFromPipelineByPropertyName)]
[datetime]$TimeStamp,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$User = $Script:User
)
DynamicParam {
$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
#region parameterRetry
$paramRetry = New-Object -Type System.Management.Automation.RuntimeDefinedParameter
$paramRetry_Attributes = New-Object System.Management.Automation.ParameterAttribute
$paramRetry.Name = 'Retry'
$paramRetry.ParameterType = [int32]
$paramRetry_Attributes.ParameterSetName = "__AllParameterSets"
$paramRetry_Attributes.Mandatory = if ($Priority -eq 'Emergency') {$true} else {$false}
$paramRetry_Attributes.ValueFromPipelineByPropertyName = $true
$paramRetry_ValidateNotNullOrEmpty = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
$paramRetry_ValidateScript = New-Object System.Management.Automation.ValidateScriptAttribute({$_ -ge 30})
$paramRetry.Attributes.Add($paramRetry_Attributes )
$paramRetry.Attributes.Add($paramRetry_ValidateNotNullOrEmpty)
$paramRetry.Attributes.Add($paramRetry_ValidateScript)
$paramDictionary.Add($paramRetry.Name, $paramRetry)
#endregion parameterRetry
#region parameterExpire
$paramExpire = New-Object -Type System.Management.Automation.RuntimeDefinedParameter
$paramExpire_Attributes = New-Object System.Management.Automation.ParameterAttribute
$paramExpire.Name = 'Expire'
$paramExpire.ParameterType = [int32]
$paramExpire_Attributes.ParameterSetName = "__AllParameterSets"
$paramExpire_Attributes.Mandatory = if ($Priority -eq 'Emergency') {$true} else {$false}
$paramExpire_Attributes.ValueFromPipelineByPropertyName = $true
$paramExpire_ValidateNotNullOrEmpty = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
$paramExpire_ValidateScript = New-Object System.Management.Automation.ValidateScriptAttribute({$_ -le 86400})
$paramExpire.Attributes.Add($paramExpire_Attributes )
$paramExpire.Attributes.Add($paramExpire_ValidateNotNullOrEmpty)
$paramExpire.Attributes.Add($paramExpire_ValidateScript)
$paramDictionary.Add($paramExpire.Name, $paramExpire)
#endregion parameterExpire
#region parameterSound
$paramSound = New-Object -Type System.Management.Automation.RuntimeDefinedParameter
$paramSound_Attributes = New-Object System.Management.Automation.ParameterAttribute
$paramSound.Name = 'Sound'
$paramSound.ParameterType = [string]
$paramSound_Attributes.ParameterSetName = "__AllParameterSets"
$paramSound_Attributes.Mandatory = $false
$paramSound_Attributes.ValueFromPipelineByPropertyName = $true
$paramSound_ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $pushoverSounds
$paramSound.Attributes.Add($paramSound_Attributes )
$paramSound.Attributes.Add($paramSound_ValidateSet)
$paramDictionary.Add($paramSound.Name, $paramSound)
#endregion parameterSound
#region parameterDevice
$paramDevice = New-Object -Type System.Management.Automation.RuntimeDefinedParameter
$paramDevice_Attributes = New-Object System.Management.Automation.ParameterAttribute
$paramDevice.Name = 'Device'
$paramDevice.ParameterType = [string]
$paramDevice_Attributes.ParameterSetName = "__AllParameterSets"
$paramDevice_Attributes.Mandatory = $false
$paramDevice_Attributes.ValueFromPipelineByPropertyName = $true
$paramDevice_ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $pushoverDevices
$paramDevice_ValidateScript = New-Object System.Management.Automation.ValidateScriptAttribute({$_ -match '^[A-Za-z0-9_-]{1,25}$'})
$paramDevice.Attributes.Add($paramDevice_Attributes )
$paramDevice.Attributes.Add($paramDevice_ValidateSet)
$paramDevice.Attributes.Add($paramDevice_ValidateScript)
$paramDictionary.Add($paramDevice.Name, $paramDevice)
#endregion parameterDevice
return $paramDictionary
}
BEGIN
{
$Uri =
$SystemWebProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$WebRequest = @{
'Uri' = "https://api.pushover.net/1/messages.json"
'Method' = [System.Net.WebRequestMethods+Http]::Post
}
if (!$SystemWebProxy.IsBypassed($WebRequest.Uri)) {
$Proxy = $SystemWebProxy.GetProxy($WebRequest.Uri)
$WebRequest.Add('Proxy',$Proxy.AbsoluteUri)
$WebRequest.Add('ProxyUseDefaultCredentials',$true)
}
$PriorityValues = @{
'Low' = -1
'Normal' = 0
'High' = 1
'Emergency' = 2
}
}
PROCESS
{
$Body = @{}
$PSBoundParameters.GetEnumerator() | ForEach-Object {
if ($_.Key -eq 'priority' ) { $Value = $PriorityValues[$_.Value] }
elseif ($_.Key -eq 'timestamp') { $Value = ConvertTo-UnixDateTime $_.Value }
else { $Value = $_.Value }
$Body.Add($_.Key.ToLower(), $Value)
}
if (-not $Body.ContainsKey('token')) {$Body.Add('token', $Token)}
if (-not $Body.ContainsKey('user' )) {$Body.Add('user' , $User )}
$Output = Invoke-RestMethod @WebRequest -Body $Body
if ($Output -ne $null) {
$Output.Status = [bool]$Output.Status
Write-Output $Output
}
}
}
Export-ModuleMember -Function Send-PushoverMessage
function Get-PushoverMessageFromClipboard {
Add-Type -Assembly PresentationCore
if ([Windows.Clipboard]::ContainsFileDropList()) {
$clipLines = [Windows.Clipboard]::GetFileDropList()
}
if ([Windows.Clipboard]::ContainsText()) {
$clipLines = [Windows.Clipboard]::GetText().split("`n")
}
foreach ($clipLine in $clipLines) {
$clipUrl = $null
$IsUrl = [System.Uri]::TryCreate($clipLine, [System.UriKind]::Absolute, [ref]$clipUrl)
if ($IsUrl) {
if ($clipUrl.IsFile -or $clipUrl.IsUNC) {
$Output = [pscustomobject]@{
'Title' = 'Clipped File'
'Message' = Split-Path -Leaf $clipLine
'Url' = $clipUrl.LocalPath
}
}
else {
$Output = [pscustomobject]@{
'Title' = 'Clipped URL'
'Message' = $clipUrl.AbsoluteUri
'Url_Title' = 'Open Link'
'Url' = $clipUrl.AbsoluteUri
}
}
Write-Output $Output
}
else {
$Output = [pscustomobject]@{
'Title' = 'Clipped Text'
'Message' = $clipLine
}
Write-Output $Output
}
}
}
Export-ModuleMember -Function Get-PushoverMessageFromClipboard
function Set-PushoverTokens {
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$User
)
PROCESS
{
$Script:Token = $Token
$Script:User = $User
}
}
Export-ModuleMember -Function Set-PushoverTokens
function Export-PushoverTokens {
param(
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$User = $Script:User,
[Parameter(Mandatory)]
[ValidateScript({Test-Path (Split-Path -Parent $_)})]
[string]$FilePath
)
$PushoverTokens = [pscustomobject]@{
'Token' = ConvertTo-SecureString -AsPlainText -Force $Token | ConvertFrom-SecureString
'User' = ConvertTo-SecureString -AsPlainText -Force $User | ConvertFrom-SecureString
}
$PushoverTokens | Export-Clixml -Path $FilePath -ErrorAction Stop
Write-Output $(Get-Item -Path $FilePath)
}
Export-ModuleMember -Function Export-PushoverTokens
function Import-PushoverCachedTokens {
param(
[Parameter()]
[ValidateScript({Test-Path $_})]
[string]$FilePath = $Script:TokenFile,
[Parameter()]
[switch]$Passthrough
)
$ImportedObject = Import-Clixml -Path $FilePath
$ValidatedTokens = New-Object -TypeName PSObject
if (($ImportedObject -ne $null) -and ($ImportedObject -is [psobject])) {
$ImportedObject.PSObject.Properties | ForEach-Object {
Write-Verbose ("Found Property '{0}' with value '{1}'" -f $_.Name, $_.Value)
$Member = @{
'InputObject' = $ValidatedTokens
'MemberType' = 'NoteProperty'
'Name' = $_.Name
'Value' = ConvertTo-SecureString $_.Value | Convert-SecureStringToPlainText
}
if ($Member.Value -match '^[A-Za-z0-9]{30}$') {
Add-Member @Member
}
else {
throw ("Imported {0} is not a valid Pushover.net token." -f $_.Name)
}
}
$ValidatedTokens | Set-PushoverTokens
if ($Passthrough) {Write-Output $ValidatedTokens}
}
else {
Set-PushoverTokens
Export-PushoverTokens -FilePath $FilePath
}
}
Export-ModuleMember -Function Import-PushoverCachedTokens
function Register-PushoverJobWatcher {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[System.Management.Automation.Job]$Job,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9_-]{1,25}$'})]
[string]$Device,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$Token = $Script:Token,
[Parameter()]
[ValidateScript({$_ -match '^[A-Za-z0-9]{30}$'})]
[string]$User = $Script:User
)
PROCESS {
$Event = Register-ObjectEvent -InputObject $Job -EventName StateChanged -MessageData $PSBoundParameters -Action {
$JobState = $Event.SourceEventArgs.JobStateInfo.State.ToString()
$Message = @{
'Title' = "PS Job [{0}]" -f $Sender.Name
'Message' = $JobState.ToUpper()
'TimeStamp' = $Event.TimeGenerated
}
if ($Sender.HasMoreData) {$Message.Message = $Message.Message + ' + DATA'}
$Event.MessageData.GetEnumerator() | ForEach-Object {
if ($_.Key -ne 'Job') { $Message.Add($_.Key,$_.Value) }
}
if ($JobState -eq 'Failed') {$Message.Add('Priority',"High")}
Send-PushoverMessage @Message
Write-Verbose ("{0}:Job {1}." -f $Sender.Name, $JobState )
if ($JobState -in @('Completed','Failed')) {
$Subscriber = Get-EventSubscriber -SourceIdentifier $event.sourceidentifier
Unregister-Event $Subscriber.SourceIdentifier
Remove-Job $Subscriber.Action
}
}
Write-Output $Event
}
}
Export-ModuleMember -Function Register-PushoverJobWatcher
#endregion Functions
#region ModuleLoad
Import-PushoverCachedTokens
$pushoverTokenFile = Get-Item $TokenFile
Export-ModuleMember -Variable pushoverTokenFile
$pushoverUser = Get-PushOverUser
if ($pushoverUser.Status -ne 1) {throw "Could not validate Pushover.net User Key with value '$User'"}
Set-Variable -Name pushoverDevices -Option ReadOnly -Value ($pushoverUser.devices) -Force
Export-ModuleMember -Variable pushoverDevices
Set-Variable -Name pushoverSounds -Option ReadOnly -Value (Get-PushoverSounds) -Force
Export-ModuleMember -Variable pushoverSounds
#endregion ModuleLoad
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
Remove-Variable pushoverDevices -Force -scope Global
Remove-Variable pushoverSounds -Force -scope Global
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment