PrtgAPI.GoPrtg.ps1

# GoPrtg Shared Functions

function GetServers
{
    $servers = __goPrtgGetServers | ConvertFrom-Csv -Header "Server","Alias","UserName","PassHash"

    foreach($server in $servers)
    {
        if($server.Alias -eq "")
        {
            $server.Alias = $null
        }
    }

    return $servers
}

function GetGoPrtgStart($contents)
{
    for($i = 0; $i -lt $contents.Length; $i++)
    {
        if($contents[$i] -eq "########################### Start GoPrtg Servers ###########################")
        {
            return $i
        }
    }
    
    return $null
}

function GetGoPrtgEnd($contents)
{
    if($functionStart -ne -1)
    {
        for($i = $functionStart + 1; $i -lt $contents.Length; $i++)
        {
            if($contents[$i] -eq "############################ End GoPrtg Servers ############################")
            {
                return $i
            }
        }
    }

    return $null
}

function GetPre($contents, $functionStart)
{
    $index = 0

    $pre = $contents | ForEach-Object {
        if($index -lt $functionStart)
        {
            $_
        }

        $index++
    }

    return $pre
}

function GetPost($contents, $functionEnd)
{
    $index = 0

    $post = $contents | ForEach-Object {
        if($index -gt $functionEnd)
        {
            return $_
        }

        $index++
    }

    return $post
}

function ValidateGoPrtgBlock
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSPossibleIncorrectComparisonWithNull", "", Scope="Function")]
    param($functionStart, $functionEnd, $functionExists)

    if(!$functionExists -and $null -eq $functionStart -and $null -eq $functionEnd)
    {
        return
    }

    if($null -eq $functionStart -and $null -ne $functionEnd)
    {
        throw "GoPrtg Servers start line '########################### Start GoPrtg Servers ###########################' has been removed from PowerShell profile. Please reinstate line or remove all lines pertaining to GoPrtg from your profile."
    }

    if($null -ne $functionStart -and $null -eq $functionEnd)
    {
        throw "GoPrtg Servers end line '############################ End GoPrtg Servers ############################' has been removed from PowerShell profile. Please reinstate line or remove all lines pertaining to GoPrtg from your profile."
    }

    if($null -ne $functionStart -and $null -ne $functionEnd)
    {
        if(!$functionExists)
        {
            throw "GoPrtg header and footer are present in PowerShell profile, however __goPrtgGetServers function was not loaded into the current session. Please verify the function has not been corrupted or remove the GoPrtg header and footer and re-run Install-GoPrtgServer."
        }
    }
}

function AddGoPrtgFunctionHeaderAndFooter($contents, $funcBody, $functionStart, $functionEnd)
{
    $formatted = @()
    $formatted += $contents[$functionStart + 2]
    $formatted += $funcBody
    $formatted += $contents[$functionEnd - 2]

    return $formatted
}

function UpdateGoPrtgFunctionBody($funcBody, $contents, $functionStart, $functionEnd)
{
    $pre = GetPre $contents $functionStart
    $post = GetPost $contents $functionEnd

    $newContent = @()
    $newContent += $pre

    if($null -ne $funcBody)
    {
        $funcBody = AddGoPrtgHeaderAndFooter $contents $funcBody $functionStart $functionEnd
        $newContent += $funcBody
    }

    $newContent += $post

    $str = [string]::Join("`r`n", $newContent)

    if($str -eq "")
    {
        Set-Content $Profile $str -NoNewline
    }
    else
    {
        Set-Content $Profile $str
    }
}

function AddGoPrtgHeaderAndFooter($contents, $funcBody, $functionStart, $functionEnd)
{
    $formatted = @()
    $formatted += $contents[$functionStart]
    $formatted += $contents[$functionStart + 1]
    $formatted += $funcBody
    $formatted += $contents[$functionEnd - 1]
    $formatted += $contents[$functionEnd]

    return $formatted
}

function UpdateServerRunner([ScriptBlock]$script)
{
    if(!(Test-Path $Profile))
    {
        throw "No GoPrtg servers are installed. To install a GoPrtg server, run Install-GoPrtgServer."
    }

    $contents = Get-Content $Profile

    $functionStart = GetGoPrtgStart $contents
    $functionEnd = GetGoPrtgEnd $contents $functionStart

    $functionExists = Get-Command __goPrtgGetServers -ErrorAction SilentlyContinue

    ValidateGoPrtgBlock $functionStart $functionEnd $functionExists

    if($functionExists)
    {
        if(!(Get-PrtgClient))
        {
            throw "You are not connected to a PRTG Server. Please connect first using GoPrtg [<server>]."
        }

        $client = Get-PrtgClient

        $servers = @(GetServers)

        #todo: when we update the aliases in the file, we need to also update them in memory!! is that even happening?

        $targetServer = @($servers | Where-Object {$_.Server -eq $client.Server -and $_.UserName -eq $client.UserName })

        if($targetServer.Count -eq 0)
        {
            $serverIgnoringUserName = @($servers | Where-Object {$_.Server -eq $client.Server})

            if($serverIgnoringUserName.Count -eq 0)
            {
                throw "Server '$($client.Server)' is not a valid GoPrtg server. To install this server, run Install-GoPrtgServer [<alias>]"
            }
            else
            {
                throw "Server '$($client.Server)' is a valid GoPrtg server, however you are not authenticated as a valid user for this server. To modify this server, first run GoPrtg [<alias>], then re-run the original command"
            }
        }
        elseif($targetServer.Count -eq 1)
        {
            $rowUpdater = & $script $servers $targetServer

            UpdateServerRow $contents $targetServer $functionStart $functionEnd $rowUpdater
        }
        elseif($targetServer.Count -gt 1)
        {
            throw "A critical error has occurred: more than one server exists with the same server and username. Please remove the duplicate from your $Profile manually."
        }
    }
    else
    {
        throw "GoPrtg is not installed. Run Install-GoPrtgServer <alias> to install a server with the specified alias."
    }
}

function UpdateServerRow($contents, $server, $functionStart, $functionEnd, [ScriptBlock]$updater)
{
    $new = @()

    for($i = $functionStart + 3; $i -le $functionEnd -3; $i++)
    {
        $line = $contents[$i]

        $compare = $null

        # Get the text of the server based on whether or not it has an alias

        if($null -ne $server.Alias -and $server.Alias -ne "")
        {
            $compare = " `"```````"$($server.Server)```````",```````"$($server.Alias)```````",```````"$($server.UserName)```````",```````"$($server.PassHash)```````"`"*"
        }
        else
        {
            $compare = " `"```````"$($server.Server)```````",,```````"$($server.UserName)```````",```````"$($server.PassHash)```````"`"*"
        }

        if($line -like $compare)
        {
            $newLine = $null

            # Update the value of this line based on whether or not our alias is empty

            $newLine = & $updater $server {
                param($server, $alias, $username, $passhash)

                CreateNewLine $server $alias $username $passhash
            }

            if($line.EndsWith(","))
            {
                $newLine += ","
            }

            $new += $newLine
        }
        else
        {
            $new += $line
        }        
    }

    $final = AddGoPrtgFunctionHeaderAndFooter $contents $new $functionStart $functionEnd

    UpdateGoPrtgFunctionBody $final $contents $functionStart $functionEnd

    .([ScriptBlock]::Create(($final -replace "function ","function global:")))
}

function CreateNewLine($server, $alias, $username, $passhash)
{
    $newLine = ""

    if($null -ne $alias -and $alias -ne "")
    {
        $newLine = " `"```"$($server)```",```"$alias```",```"$($userName)```",```"$($passHash)```"`""
    }
    else
    {
        $newLine = " `"```"$($server)```",,```"$($userName)```",```"$($passHash)```"`""
    }

    return $newLine
}

function Write-ColorOutput($Object, $ForegroundColor)
{
    $initialColor = $Host.UI.RawUI.ForegroundColor

    if($initialColor -ne -1)
    {
        $Host.UI.RawUI.ForegroundColor = $ForegroundColor
    }

    $Object | Write-Output

    if($initialColor -ne -1)
    {
        $Host.UI.RawUI.ForegroundColor = $initialColor
    }

    #need to test piping something with a header
}


#todo: all of these tests should test extracting the passhash and confirming it decrypts to the original value


#todo: run invoke-scriptanalyzer when finished

#todo: make the passhash encrypted again, change the should be's to should belike's

#todo: if you remove the last server, the second last server should remove its semicolon

#push notes

#maybe we should consider using the password vault?
#https://www.powershellgallery.com/packages/BetterCredentials/4.3/Content/BetterCredentials.psm1

#note: if we use the windows credential store, our module wont be cross platform compatible

# Export nothing
try
{
    Export-ModuleMember -Function Connect-GoPrtgServer, Get-GoPrtgServer, Install-GoPrtgServer, Set-GoPrtgAlias, Uninstall-GoPrtgServer, Update-GoPrtgCredential
}
catch
{
}