CareLink.psm1

<#
.SYNOPSIS
A PowerShell Module to interact with Minimed CareLink
.DESCRIPTION
This PowerShell module emulates browser interaction to Minimed CareLink to retrieve a patient's blood glucose data
#>


function Get-CareLinkToken {
    param (
        #The Carelink username
        [parameter(ParameterSetName = 'ManualCred', Mandatory = $true, Position = 0)]
        [string]$username,
        #The Carelink password
        [parameter(ParameterSetName = 'ManualCred', Mandatory = $true, Position = 1)]
        [string]$password,
        #PowerShell credential object that contains username/password
        [parameter(ParameterSetName = 'PSCredential', Mandatory = $true, Position = 2)]
        [PSCredential]$Credential,
        #Browser to impersonate,
        [parameter(Mandatory = $false, Position = 3)]
        [ValidateSet("InternetExplorer", "Firefox", "Chrome", "Opera", "Safari")]
        [string]$BrowserName="Chrome"
    )

    #login page
    $loginPage = "https://carelink.minimed.com/patient/sso/login?country=us&lang=en"
    $login = Invoke-WebRequest -Uri $loginPage -UserAgent $userAgent -UseBasicParsing -SessionVariable medtronic
    $sessionID = ($login.InputFields | Where-Object { $_.name -eq "sessionID" }).Value
    $sessionDataOne = ($login.InputFields | Where-Object { $_.name -eq "sessionData" }).Value

    #define the user agent string
    $userAgentList = [Microsoft.PowerShell.Commands.PSUserAgent].GetProperties() | Select-Object Name, @{Name = 'UserAgent'; Expression = { [Microsoft.PowerShell.Commands.PSUserAgent]::$($_.Name) } }
    $userAgent = $userAgentList | Where-Object { $_.Name -eq $BrowserName }

    #if the credentials came in via PSCredentialObject, copy them to update variable names to be used later
    if ($PsCmdlet.ParameterSetName -eq "PSCredential")
    {
        $username = $credential.UserName
        $password = $credential.GetNetworkCredential().Password
    }

    #login to carelink
    $loginBody = @{
        "sessionID"    = $sessionID
        "sessionData"  = $sessionDataOne
        "locale"       = "en"
        "action"       = "login"
        "actionButton" = "Log in"
        "username"     = $username
        "password"     = $password
    }
    $loginResponse = Invoke-WebRequest -Uri "https://mdtlogin.medtronic.com/mmcl/auth/oauth/v2/authorize/login" -Body $loginBody -Method "POST" -UserAgent $userAgent -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -WebSession $medtronic

    #since we're using basic parsing in the above call to avoid the Internet Explorer dependency, we need to convert the response's Content to HTML to fish out the sessionData value
    $loginResponseHTML = New-Object -comobject "HTMLFile"
    $loginResponseBytes = [System.Text.Encoding]::Unicode.GetBytes($loginResponse.content)
    $loginResponseHTML.write($loginResponseBytes)
    $sessionDataTwo = $loginResponseHTML.getElementById("sessionData").value

    #consent, use the sessionId and sessionData from the login response
    $consentBody = @{
        "action"        = "consent"
        "response_mode" = "query"
        "response_type" = "code"
        "sessionID"     = $sessionId
        "sessionData"   = $sessionDataTwo
    }
    $consentPageResponse = Invoke-WebRequest -Uri "https://mdtlogin.medtronic.com/mmcl/auth/oauth/v2/authorize/consent" -Body $consentBody -Method "POST" -UserAgent $userAgent -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -WebSession $medtronic

    #get the auth token and its expiration date from the cookies in the $medtronic websession
    $authTmpToken = $medtronic.cookies.GetCookies("https://carelink.minimed.com") | Where-Object { $_.Name -eq "auth_tmp_token" }
    $cTokenValidTo = $medtronic.cookies.GetCookies("https://carelink.minimed.com") | Where-Object { $_.Name -eq "c_token_valid_to" }

    #turn into a PSCustomObject for subsequent use
    $token = [PSCustomObject] @{
        Token           = $authTmpToken
        TokenExpiration = $cTokenValidTo
        Websession      = $medtronic
        UserAgent       = $userAgent
        username        = $username
        password        = $password
    }

    return $token
}

#retrieve user account information such as their Login Date, Account ID, and User Role
function Get-CareLinkAccount {
    param (
        #The Carelink username
        [parameter(Mandatory = $true, Position = 0)]
        [PSCustomObject]$Token
    )

    #verify the token is still valid before proceding
    $token = Confirm-CarelinkToken -Token $token

    $authHeader = @{
        "Accept"          = "application/json, text/plain, */*"
        "Accept-Encoding" = "gzip, deflate, br"
        "Accept-Language" = "en-US,en;q=0.6"
        "Authorization"   = "Bearer $($Token.Token.value)"
        "Referer"         = "https://carelink.minimed.com/app/home"
        "Sec-Fetch-Dest"  = "empty"
        "Sec-Fetch-Mode"  = "cors"
        "Sec-Fetch-Site"  = "same-origin"
        "Sec-GPC"         = "1"
      }

    #call the Me rest endpoint
    $me = Invoke-RestMethod -Uri "https://carelink.minimed.com/patient/users/me" -Method "GET" -header $authHeader -UserAgent $token.userAgent -WebSession $token.websession
    return $me
}

#retrieve user profile information, username, phone number, etc.
function Get-CareLinkProfile {
    param (
        #The Carelink username
        [parameter(Mandatory = $true, Position = 0)]
        [PSCustomObject]$Token
    )

    #verify the token is still valid before proceding
    $token = Confirm-CarelinkToken -Token $token

    $authHeader = @{
        "Accept"          = "application/json, text/plain, */*"
        "Accept-Encoding" = "gzip, deflate, br"
        "Accept-Language" = "en-US,en;q=0.6"
        "Authorization"   = "Bearer $($Token.Token.value)"
        "Referer"         = "https://carelink.minimed.com/app/home"
        "Sec-Fetch-Dest"  = "empty"
        "Sec-Fetch-Mode"  = "cors"
        "Sec-Fetch-Site"  = "same-origin"
        "Sec-GPC"         = "1"
      }

    #call the Me rest endpoint
    $me = Invoke-RestMethod -Uri "https://carelink.minimed.com/patient/users/me/profile" -Method "GET" -header $authHeader -UserAgent $token.userAgent -WebSession $token.websession
    return $me
}

function Get-CareLinkData {
    param (
        #The Carelink username
        [parameter(Mandatory = $true, Position = 0)]
        [PSCustomObject]$Token,
        #The Carelink Account User
        [parameter(Mandatory = $true, Position = 1)]
        [PSCustomObject]$CarelinkUserProfile,
        #The Carelink User's Profile
        [parameter(Mandatory = $true, Position = 2)]
        [PSCustomObject]$CarelinkUserAccount
    )

    #verify the token is still valid before proceding
    $token = Confirm-CarelinkToken -Token $token

    #authentication header to make the request
    $authHeader = @{
        "Accept"          = "application/json, text/plain, */*"
        "Accept-Encoding" = "gzip, deflate, br"
        "Accept-Language" = "en-US,en;q=0.6"
        "Authorization"   = "Bearer $($Token.Token.value)"
        "Referer"         = "https://carelink.minimed.com/app/home"
        "Sec-Fetch-Dest"  = "empty"
        "Sec-Fetch-Mode"  = "cors"
        "Sec-Fetch-Site"  = "same-origin"
        "Sec-GPC"         = "1"
      }

    #pump, sensor, and glucose
    $payload = @{
        "username" = $CarelinkUserProfile.username
        "role"     = $CarelinkUserAccount.role
    } | ConvertTo-Json

  $data = Invoke-RestMethod -Uri "https://clcloud.minimed.com/connect/v2/display/message" -Method "POST" -body $payload -header $authHeader -UserAgent $token.userAgent -WebSession $token.websession
  return $data
}

function Confirm-CareLinkToken {
    param (
        #The Carelink token to validate
        [parameter(Mandatory = $true, Position = 0)]
        [PSCustomObject]$Token
    )

    #convert the token expiration string to a datetime object to compare
    #string/datetime conversion, https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-powershell-1.0/ee692801(v=technet.10)?redirectedfrom=MSDN
    $expiration = [datetime]::ParseExact($token.TokenExpiration.Value.Split("UTC")[0].Trim(), "ddd MMM dd HH:mm:ss", $null)
    $nowUTC = (Get-Date).ToUniversalTime()

    #compare the expiration date to now
    if ($nowUTC -gt $expiration)
    {
        Write-Output "Token expired. Renewing..."
        $newToken = Get-CareLinkToken -username $token.username -password $token.password
        return $newToken
    }
    else
    {
        Write-Output "Token valid"
        return $token
    }
}