PSGerickeUtil.psm1

<#
.SYNOPSIS
Sets the log path for the script.
 
.DESCRIPTION
The Set-LogPath function sets the path where logs will be stored. If the specified folder path does not exist, it will be created.
 
.PARAMETER Path
The full path where logs will be stored. This parameter is mandatory.
 
.PARAMETER WhatIf
Shows what would happen if the cmdlet runs. The cmdlet is not run.
 
.PARAMETER Confirm
Prompts you for confirmation before running the cmdlet.
 
.EXAMPLE
Set-LogPath -Path "C:\Logs\MyLog.txt"
 
This example sets the log path to "C:\Logs\MyLog.txt". If the "C:\Logs" folder does not exist, it will be created.
 
.NOTES
Author: Your Name
Date: Today's Date
#>

function Set-LogPath {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [String]
        $Path
    )
 
    trap {
        throw "Error setting log path: $($_.Exception.Message)"
    }
 
    $folderPath = Split-Path -Path $Path
    if (-not (Test-Path -Path $folderPath)) {
        New-Item -Path $folderPath -ItemType Directory -Force -ErrorAction Stop
    }
 
    if ($PSCmdlet.ShouldProcess("script variable", "Set path of the logfile")) {
        $script:logPath = $Path
    }
}

<#
.SYNOPSIS
Sets the Pushover api key for REST API calls.
 
.DESCRIPTION
The Set-RestApiKeyForPushoverApi function sets a secure Pushover api key for use in REST API calls. The user key is stored in a script-scoped variable for later use.
 
.PARAMETER ApiKey
The secure string representing the Pushover user key. This parameter is mandatory.
 
.PARAMETER WhatIf
Shows what would happen if the cmdlet runs. The cmdlet is not run.
 
.PARAMETER Confirm
Prompts you for confirmation before running the cmdlet.
 
.EXAMPLE
PS C:\> $secureUserKey = Read-Host "Enter Pushover User Key" -AsSecureString
PS C:\> Set-RestApiKeyForPushoverApi -UserKey $secureUserKey
 
This example prompts the user to enter a Pushover user key securely and then sets it using the Set-RestApiKeyForPushoverUser function.
 
.NOTES
Author: Your Name
Date: YYYY-MM-DD
#>

function Set-RestApiKeyForPushoverApi {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [securestring]
        $ApiKey
    )
 
    if ($PSCmdlet.ShouldProcess("script variable", "Set api key")) {
        $script:pushoverApiKey = $ApiKey
    }
}

<#
.SYNOPSIS
Sets the Pushover user key for REST API calls.
 
.DESCRIPTION
The Set-RestApiKeyForPushoverUser function sets a secure Pushover user key for use in REST API calls. The user key is stored in a script-scoped variable for later use.
 
.PARAMETER UserKey
The secure string representing the Pushover user key. This parameter is mandatory.
 
.PARAMETER WhatIf
Shows what would happen if the cmdlet runs. The cmdlet is not run.
 
.PARAMETER Confirm
Prompts you for confirmation before running the cmdlet.
 
.EXAMPLE
PS C:\> $secureUserKey = Read-Host "Enter Pushover User Key" -AsSecureString
PS C:\> Set-RestApiKeyForPushoverUser -UserKey $secureUserKey
 
This example prompts the user to enter a Pushover user key securely and then sets it using the Set-RestApiKeyForPushoverUser function.
 
.NOTES
Author: Your Name
Date: YYYY-MM-DD
#>


function Set-RestApiKeyForPushoverUser {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [securestring]
        $UserKey
    )
 
    if ($PSCmdlet.ShouldProcess("script variable", "Set user key")) {
        $script:pushoverUserKey = $UserKey
    }
}

<#
.SYNOPSIS
    Sends a push notification using the Pushover service.
 
.DESCRIPTION
    This function sends a push notification to a specified device using the Pushover service.
    It requires a user key and an API key for authentication.
 
.PARAMETER UserKey
    The user key for the Pushover service. This is a secure string.
 
.PARAMETER ApiKey
    The API key for the Pushover service. This is a secure string.
 
.PARAMETER Message
    The message to be sent. This parameter is mandatory.
 
.PARAMETER Device
    The device to which the message should be sent. Valid values are "iPadPro2020", "iPhone13Pro", and "iPhoneBI".
 
.PARAMETER Title
    The title of the message.
 
.PARAMETER Url
    A URL to be included with the message.
 
.PARAMETER UrlTitle
    The title of the URL.
 
.PARAMETER Priority
    The priority of the message. Valid values are "Lowest", "Low", "Normal", "High", and "Emergency".
 
.PARAMETER Sound
    The sound to be played with the message. Valid values are "pushover", "bike", "bugle", "cashregister", "classical", "cosmic", "falling", "gamelan", "incoming", "intermission", "magic", "mechanical", "pianobar", "siren", "spacealarm", "tugboat", "alien", "climb", "persistent", "echo", "updown", and "none".
 
.EXAMPLE
    Send-Pushover -UserKey $userKey -ApiKey $apiKey -Message "Test message" -Device "iPhone13Pro" -Priority "Normal" -Sound "pushover"
 
    This example sends a test message to the device "iPhone13Pro" with normal priority and the default "pushover" sound.
 
.EXAMPLE
    Send-Pushover -UserKey $userKey -ApiKey $apiKey -Message "Urgent message" -Priority "Emergency" -Sound "siren"
     
    This example sends an urgent message with emergency priority and the "siren" sound.
 
.NOTES
    Author: Stefan Gericke
    Date: 2024-11-06
#>

function Send-Pushover {
    [CmdletBinding()]
    Param(
        [securestring]
        $UserKey,

        [securestring]
        $ApiKey,

        [Parameter(Mandatory = $true)]
        [string]
        $Message,

        [ValidateSet("iPadPro2020", "iPhone13Pro", "iPhoneBI")]
        [string]
        $Device,

        [string]
        $Title,

        [string]
        $Url,

        [string]
        $UrlTitle,

        [ValidateSet("Lowest", "Low", "Normal", "High", "Emergency")]
        [string]
        $Priority,

        [ValidateSet("pushover", "bike", "bugle", "cashregister", "classical", "cosmic", "falling", "gamelan", "incoming", "intermission", "magic", "mechanical", "pianobar", "siren", "spacealarm", "tugboat", "alien", "climb", "persistent", "echo", "updown", "none")]
        [string]
        $Sound
    )

    # Check if user key and/or API key is available for doing web request
    if ([string]::IsNullOrEmpty($script:pushoverUserKey) -or $script:pushoverUserKey -ne $UserKey) {
        if ([string]::IsNullOrEmpty($UserKey) -and [string]::IsNullOrEmpty($script:pushoverUserKey)) {
            Write-Error "User key is mandatory to send a push notification!"
        }
        elseif (![string]::IsNullOrEmpty($UserKey)) {
            Set-RestApiKeyForPushoverUser -UserKey $UserKey
        }
    }
    if ([string]::IsNullOrEmpty($script:pushoverApiKey) -or $script:pushoverApiKey -ne $ApiKey) {
        if ([string]::IsNullOrEmpty($ApiKey) -and [string]::IsNullOrEmpty($script:pushoverApiKey)) {
            Write-Error "Api key is mandatory to send a push notification!"
        }
        elseif (![string]::IsNullOrEmpty($ApiKey)) {
            Set-RestApiKeyForPushoverApi -ApiKey $ApiKey
        }
    }

    # Map priority string to integer value
    switch ($Priority) {
        "Lowest" { $iPriority = -2 }
        "Low" { $iPriority = -1 }
        "Normal" { $iPriority = 0 }
        "High" { $iPriority = 1 }
        "Emergency" { $iPriority = 2 }
        Default { $iPriority = 0 }
    }

    # Prepare data for the request
    $data = @{
        token   = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($script:pushoverApiKey))
        user    = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($script:pushoverUserKey))
        message = $Message
    }

    if ($Device) { $data.Add("device", $Device) }
    if ($Title) { $data.Add("title", $Title) }
    if ($Url) { $data.Add("url", $Url) }
    if ($UrlTitle) { $data.Add("url_title", $UrlTitle) }
    if ($Sound) { $data.Add("sound", $Sound) }
    if ($Priority) {
        $data.Add("priority", $iPriority)
        if ($iPriority -eq 2) {
            $data.Add("retry", 30)
            $data.Add("expire", 1800)
        }
    }

    # Debug output if enabled
    if ($DebugPreference) {
        foreach ($key in $data.Keys) {
            Write-Debug -Message "Parameter: $key, Value: $($data[$key])"
        }
    }

    try {
        $uri = "https://api.pushover.net/1/messages.json"
        Write-Debug "Uri: $($uri)"
        $null = Invoke-RestMethod -Method Post -Uri $uri -Body $data
    }
    catch {
        Write-Error "Web request not successful: $($_.ErrorDetails)"
    }
}

<#
.SYNOPSIS
    Retrieves information about users with Enterprise Voice enabled in Microsoft Teams.
 
.DESCRIPTION
    This function checks if a user or all users in the tenant have Enterprise Voice enabled in Microsoft Teams.
    It can either check a specific user by their UserPrincipalName or export the information for the whole tenant to a CSV file.
 
.PARAMETER UserPrincipalName
    The UserPrincipalName of the user to check. This parameter is mandatory if you are checking a specific user.
 
.PARAMETER ExportCSV
    A switch to indicate if the results for the whole tenant should be exported to a CSV file.
 
.PARAMETER ExportPath
    The path where the CSV file should be saved. If not specified, a default path in the TEMP directory will be used.
 
.EXAMPLE
    Get-TeamsPSTNEnterpriseVoiceEnabled -UserPrincipalName "user@example.com"
 
    Retrieves information about the specified user.
 
.EXAMPLE
    Get-TeamsPSTNEnterpriseVoiceEnabled -ExportCSV
 
    Retrieves information about all users in the tenant and exports it to a CSV file in the TEMP directory.
 
.EXAMPLE
    Get-TeamsPSTNEnterpriseVoiceEnabled -ExportCSV -ExportPath "C:\Exports\EV-enabled.csv"
 
    Retrieves information about all users in the tenant and exports it to the specified CSV file.
 
.NOTES
    Author: Stefan Gericke
    Date: 2024-11-06
#>

function Get-TeamsPSTNEnterpriseVoiceEnabled {
    [CmdletBinding(DefaultParameterSetName = 'WholeTenant')]
    Param(
        [Parameter(ParameterSetName = 'UserPrincipalName', Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $UserPrincipalName,

        [Parameter(ParameterSetName = 'WholeTenant', Position = 0)]
        [switch]
        $ExportCSV,

        [Parameter(ParameterSetName = 'WholeTenant', Position = 1)]
        [ValidateNotNullOrEmpty()]
        [string]
        $ExportPath = (Join-Path -Path $Env:TEMP -ChildPath "$(Get-Date -Format yyyyMMdd_HHmm)_EV-enabled.csv")
    )

    # Check if you already have a connection to Microsoft Teams
    try {
        $null = Get-CsTenant
        Write-Debug "Connection to Microsoft Teams already established."
    }
    catch {
        Write-Warning "You are not connected to Microsoft Teams!"
        Write-Warning "Please log in with your credentials and the MFA token in your browser."
        try {
            Connect-MicrosoftTeams
            $null = Get-CsTenant
            Write-Debug "You are successfully logged in to Microsoft Teams."
        }
        catch {
            Write-Error "The log in to Microsoft Teams was not successful. The script will stop here!"
            return
        }
    }

    # Check the parameter set and collect the results
    switch ($PSCmdlet.ParameterSetName) {
        # Collect the information for the specified user
        'UserPrincipalName' {
            $results = New-Object -TypeName "System.Collections.ArrayList"
            foreach ($upn in $UserPrincipalName) {
                try {
                    Write-Debug "Request for $($upn) ..."
                    $response = Get-CsOnlineUser -Identity $upn -ErrorAction SilentlyContinue
                    if ($null -eq $response) {
                        Write-Error "UPN $($upn) can't be found"
                        continue
                    }
                    else {
                        $results.Add([PSCustomObject]@{"UserPrincipalName" = $response.UserPrincipalName; "EnterpriseVoiceEnabled" = $response.EnterpriseVoiceEnabled })
                    }
                }
                catch {
                    Write-Error $message.Exception.Message
                }
            }
        }
        'WholeTenant' {
            # Collect all users with Enterprise Voice enabled in the tenant
            Write-Verbose "Get all users on the tenant with Enterprise Voice flag enabled. This will take some time. Please wait ..."
            try {
                if ($DebugPreference) {
                    $debugNoOfUpns = 10
                    Write-Debug "Get only the first $($debugNoOfUpns) as result!"
                    $results = Get-CsOnlineUser -Filter { EnterpriseVoiceEnabled -eq "true" } -ResultSize $debugNoOfUpns -ErrorAction Stop | Select-Object -Property UserPrincipalName, Alias, EnterpriseVoiceEnabled
                }
                else {
                    $results = Get-CsOnlineUser -Filter { EnterpriseVoiceEnabled -eq "true" } -ErrorAction Stop | Select-Object -Property UserPrincipalName, Alias, EnterpriseVoiceEnabled
                }
                if ($ExportCSV -or !([string]::IsNullOrEmpty($ExportPath))) {
                    $results | Export-Csv -Path $ExportPath -NoTypeInformation
                }
            }
            catch {
                $errorMessage = $_.Exception.Message
                if ($errorMessage -eq "Access Denied.") {
                    Write-Error "No Permission to run this command!"
                }
                Disconnect-MicrosoftTeams
                return
            }
        }
    }
    return $results
}

<#
.SYNOPSIS
    Creates a symbolic link, hard link, or junction.
 
.DESCRIPTION
    This script creates a symbolic link, hard link, or junction at the specified path.
    Administrator permissions are required to create symbolic links and junctions.
 
.PARAMETER LinkType
    The type of link to create. Valid values are 'SymbolicLink', 'HardLink', and 'Junction'.
 
.PARAMETER TargetPath
    The target path for the link.
 
.PARAMETER LinkPath
    The path where the link will be created.
 
.EXAMPLE
    Add-LinkOfFileFolder -LinkType SymbolicLink -TargetPath "C:\Target" -LinkPath "C:\Link"
     
    Creates a symbolic link from "C:\Link" to "C:\Target".
 
.EXAMPLE
    Add-LinkOfFileFolder -LinkType HardLink -TargetPath "C:\Target" -LinkPath "C:\Link"
 
    Creates a hard link from "C:\Link" to "C:\Target".
 
.EXAMPLE
    Add-LinkOfFileFolder -LinkType Junction -TargetPath "C:\Target" -LinkPath "C:\Link"
 
    Creates a junction from "C:\Link" to "C:\Target".
 
.NOTES
    Author: Stefan Gericke
    Date: 2024-11-07
#>


function Add-LinkOfFileFolder {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('SymbolicLink', 'HardLink', 'Junction')]
        [string]
        $LinkType,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $TargetPath,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $LinkPath
    )

    function Test-Administrator {
        $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
        return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    # Check if the target path exists
    if (!(Test-Path -Path $TargetPath)) {
        Write-Error "The target path $TargetPath does not exist."
        return
    }

    # CHeck if the link type is symbolic link or junction and if the user is an administrator
    if (($LinkType -eq 'SymbolicLink' -or $LinkType -eq 'Junction') -and !(Test-Administrator)) {
        Write-Error "Administrator permissions are required to create a symbolic link or junction."
        return
    }

    try {
        switch ($LinkType) {
            'SymbolicLink' {
                $result = New-Item -ItemType SymbolicLink -Path $LinkPath -Value $TargetPath -ErrorAction Stop
            }
            'HardLink' {
                $result = New-Item -ItemType HardLink -Path $LinkPath -Value $TargetPath -ErrorAction Stop
            }
            'Junction' {
                $result = New-Item -ItemType Junction -Path $LinkPath -Value $TargetPath -ErrorAction Stop
            }
        }
        # Check if the link was created successfully
        if (!(Test-Path -Path $LinkPath)) {
            Write-Error "The $LinkType was not created successfully."
            return
        }
        Write-Verbose "$LinkType created successfully from $LinkPath to $TargetPath"
        return $result
    }
    catch {
        Write-Error $_.Exception.Message
        return
    }
}

<#
.SYNOPSIS
    Encrypts a secure string and sets it to the clipboard.
 
.DESCRIPTION
    This function prompts the user to enter a secure string, encrypts it, and then sets the encrypted string to the clipboard.
 
.PARAMETER None
    This function does not take any parameters.
 
.PARAMETER WhatIf
    Shows what would happen if the command runs. The command is not executed.
 
.PARAMETER Confirm
    Prompts you for confirmation before running the command.
 
.EXAMPLE
    Set-ClipboardWithEncryptedString
     
    Prompts the user to enter a secure string, encrypts it, and sets the encrypted string to the clipboard.
 
.NOTES
    Author: Stefan Gericke
    Date: 2024-11-06
#>

function Set-ClipboardWithEncryptedString {
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param()

    # Prompt the user to enter a secure string
    $secureString = Read-Host -AsSecureString -Prompt "Enter the secure string you need in the clipboard"
    
    # Convert the secure string to an encrypted standard string
    $encryptedString = $secureString | ConvertFrom-SecureString
    
    # Set the encrypted string to the clipboard
    if ($PSCmdlet.ShouldProcess("clipboard", "Set encrypted string")) {
        $encryptedString | Set-Clipboard
    }
}

<#
.SYNOPSIS
    Writes a log message to a specified log file and the host.
 
.DESCRIPTION
    This function writes a log message with a specified log level to a log file and the host.
    It ensures the log message is formatted with a timestamp and the log level.
 
.PARAMETER LogMessage
    The message to be logged. This parameter is mandatory.
 
.PARAMETER LogLevel
    The level of the log message. Valid values are "Error", "Warn", "Info", "Ok", "Failed", and "Success". This parameter is mandatory.
 
.PARAMETER LogFile
    The path to the log file. If not specified, the function uses a default log path set in the script.
 
.EXAMPLE
    Write-Log -LogMessage "This is an informational message" -LogLevel "Info"
 
    Writes an informational message to the log file and the host.
 
.EXAMPLE
    Write-Log -LogMessage "An error occurred" -LogLevel "Error" -LogFile "C:\Logs\error.log"
     
    Writes an error message to the specified log file and the host.
 
.NOTES
    Author: Stefan Gericke
    Date: 2024-11-06
#>

function Write-Log {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $LogMessage,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Error", "Warn", "Info", "Ok", "Failed", "Success")]
        [string]
        $LogLevel,

        [string]
        $LogFile
    )

    # Check the LogFile variable is existing in script variable or parameter
    if ($script:logPath -ne $LogFile) {
        if (![string]::IsNullOrEmpty($LogFile)) {
            Set-LogPath -Path $LogFile
        }
    }

    # Set Date/Time
    $dateTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    # Set log level
    $strLength = $MyInvocation.MyCommand.Parameters.LogLevel.Attributes.ValidValues | Sort-Object -Property Length -Descending | Select-Object -First 1 -ExpandProperty Length
    $countSpaces = $strLength + 3 - $LogLevel.Length
    $line = $dateTime + " " + $LogLevel.ToUpper() + (" " * $countSpaces ) + $LogMessage

    try {
        $line | Out-File -FilePath $script:logPath -Append
        # Write the line on the terminal
        Write-Verbose $line
        return $line
    }
    catch {
        $message = $_
        Write-Error  "Message ($line) not added to log file: $message"
        return
    }
}

# Utilities/Write-Log.ps1
$script:logPath = Join-Path -Path $Env:TEMP -ChildPath "$(Get-Date -Format yyyyMMdd_HHmm)_Script.log"

# Rest API/Send-Pushover.ps1
$script:pushoverUserKey = New-Object -TypeName System.Security.SecureString
$script:pushoverApiKey = New-Object -TypeName System.Security.SecureString