MicrosoftHealth.psm1
#requires -Version 3 $script:AuthenticationSettingsPath = "$PSScriptRoot\Authentication.config.xml" Write-Warning "Please be aware your AccessToken and RefreshToken will be stored in the module folder`n`t`t Other users could be able to use this data to connect to your MicrosoftHealth data." #clear AccessToken,ValidThru variables when loading module Remove-Variable -Name AccessToken, RefreshToken, ValidThru -ErrorAction SilentlyContinue <# =========================================================================== Created on: 10/10/2015 12:00 PM Created by: Stefan Stranger Filename: MicrosoftHealth.psm1 ------------------------------------------------------------------------- Module Name: MicrosoftHealth Description: This Microsoft Health PowerShell module was built to give a Microsoft Band user the ability to interact with Microsoft Health data via Powershell. Before importing this module, you must create your own Healt application. To register your application in the Microsoft Account Developer Center, visit https://account.live.com/developers/applications. Once you do so, I recommend copying/pasting your Client ID and App URL to the parameters under the Get-OAuthAuthorization function. More info: http://developer.microsoftband.com/Content/docs/MS%20Health%20API%20Getting%20Started.pdf =========================================================================== #> ##################################################################################### # Helper function for MicrosoftHealth Module # Description: # To start the sign-in process within your application or web service, you need to # use a web browser or web browser control to load a URL request for the Access Token ##################################################################################### function Get-oAuth2AccessToken { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] $AuthorizeUri = 'https://login.live.com/oauth20_authorize.srf', [Parameter(Mandatory = $true)] [string] $ClientId, [Parameter(Mandatory = $true)] [string] $Secret, [Parameter(Mandatory = $false)] [string] $RedirectUri = 'https://login.live.com/oauth20_desktop.srf' ) #region - Authorization code grant flow... Add-Type -AssemblyName System.Windows.Forms $OnDocumentCompleted = { if($web.Url.AbsoluteUri -match 'code=([^&]*)') { $script:AuthCode = $Matches[1] $form.Close() } elseif($web.Url.AbsoluteUri -match 'error=') { $form.Close() } } $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{ Width = 400 Height = 750 } $web.Add_DocumentCompleted($OnDocumentCompleted) $form = New-Object -TypeName System.Windows.Forms.Form -Property @{ Width = 400 Height = 750 Autoscroll = $true } $form.Add_Shown({ $form.Activate() }) $form.Controls.Add($web) # Request Authorization Code $Scope = @('mshealth.ReadProfile', 'mshealth.ReadActivityHistory', 'mshealth.ReadDevices', 'mshealth.ReadActivityLocation', 'offline_access') $web.Navigate("$AuthorizeUri`?client_id=$ClientId&scope=$Scope&response_type=code&redirect_uri=$RedirectUri") $null = $form.ShowDialog() # Request AccessToken $Response = Invoke-RestMethod -Uri 'https://login.live.com/oauth20_token.srf' -Method Post ` -ContentType 'application/x-www-form-urlencoded' ` -Body "client_id=$ClientId&redirect_uri=$RedirectUri&client_secret=$Secret&code=$AuthCode&grant_type=authorization_code" $global:AccessToken = $Response.access_token $global:ValidThru = (Get-Date).AddSeconds([int]$Response.expires_in) $global:RefreshToken = $Response.refresh_token Write-Debug -Message ('Access token is: {0}' -f $AccessToken) #endregion # Write AccessCode and RefreshToken to file for future usage Set-AuthenticationToken -AccessToken $AccessToken -RefreshToken $RefreshToken Write-Debug -Message ('Refresh token is: {0}' -f $RefreshToken) } ##################################################################################### # Helper function for MicrosoftHealth Module # Description: # When acurrent access_token has expired, if it expires, # run the following request to redeem the refresh token for a new access token ##################################################################################### function Get-oAuth2RefreshToken { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ClientId, [Parameter(Mandatory = $true)] [string] $Secret, [Parameter(Mandatory = $false)] [string] $RedirectUri = 'https://login.live.com/oauth20_desktop.srf' ) $settings = Load-AuthenticationSettings $RefreshToken = $settings.RefreshToken $Response = Invoke-RestMethod -Uri 'https://login.live.com/oauth20_token.srf' -Method Post ` -ContentType 'application/x-www-form-urlencoded' ` -Body "client_id=$ClientId&redirect_uri=$RedirectUri&client_secret=$Secret&refresh_token=$RefreshToken&grant_type=refresh_token" $global:AccessToken = $Response.access_token $global:ValidThru = (Get-Date).AddSeconds([int]$Response.expires_in) $global:RefreshToken = $Response.refresh_token Write-Debug -Message ('Access token is: {0}' -f $AccessToken) } ################################################################################### # Helper function for MicrosoftHealth Module # Description: Builds the Access Header for the Microsoft Health Cloud REST API url ################################################################################### function Build-AccessHeader { param ($AccessToken) @{ 'Authorization' = 'Bearer ' + $AccessToken } } ################################################################################### # Helper function for MicrosoftHealth Module # Description: Helper function is called from other MicrosoftHealth Function to # retrieve Microsoft Health Data for different End point of REST API ################################################################################### function Get-MicrosoftHealthData { param ( $RequestUrl ) try { $settings = Load-AuthenticationSettings #Check for AccessToken variable and if there is no refreshtoken stored in settings file if (!($AccessToken) -and (!($settings.RefreshToken))) { Get-oAuth2AccessToken -ClientId $settings.ClientId -Secret $settings.Secret } elseif ($ValidThru -lt (Get-Date)) { Write-Verbose 'AccessToken has expired' #Get-oAuth2AccessToken -ClientId $settings.ClientId -Secret $settings.Secret Get-oAuth2RefreshToken -ClientId $settings.ClientId -Secret $settings.Secret } $headers = Build-AccessHeader -AccessToken $AccessToken Write-Verbose $RequestUrl $result = Invoke-RestMethod -Uri $RequestUrl -Method GET -Headers $headers -ContentType 'application/json' return $result } catch { 'Could not retrieve MicrosoftHealth Data' } } # .EXTERNALHELP MicrosoftHealth.psm1-help.xml Function Get-MicrosoftHealthProfile { [CmdletBinding()] [OutputType('System.String')] [Alias('ghp')] param() process { try { Get-MicrosoftHealthData -RequestUrl 'https://api.microsofthealth.net/v1/me/Profile' } catch [System.Net.WebException] { 'The remote server returned an error: (400) Bad Request.' 'Authenticate first. Run Get-oAuth2AccessToken function' } catch { 'Something went wrong' } } } # .EXTERNALHELP MicrosoftHealth.psm1-help.xml Function Get-MicrosoftHealthDevice { [CmdletBinding()] [OutputType('System.String')] [Alias('ghd')] param() process { try { $result = Get-MicrosoftHealthData -RequestUrl 'https://api.microsofthealth.net/v1/me/Devices' $result.deviceProfiles } catch [System.Net.WebException] { 'The remote server returned an error: (400) Bad Request.' 'Authenticate first. Run Get-oAuth2AccessToken function' } catch { 'Something went wrong' } } } # .EXTERNALHELP MicrosoftHealth.psm1-help.xml Function Get-MicrosoftHealthActivity { [CmdletBinding()] [OutputType('System.Management.Automation.PSCustomObject')] [Alias('gha')] param( [ValidateSet('Run', 'Bike', 'Freeplay','GuidedWorkout','Golf','Sleep')] [Parameter(Mandatory = $true)] [string]$activity, [Parameter(Mandatory = $false)] [Validatepattern('^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$')] [string]$StartTime, [Parameter(Mandatory = $false)] [Validatepattern('^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$')] [string]$EndTime, [Parameter(Mandatory = $false)] [Switch]$Details, [Parameter(Mandatory = $false)] [Switch]$MapPoints, [Parameter(Mandatory = $false)] [Switch]$MinuteSummaries) process { $params = [pscustomobject]([ordered]@{}+$PSBoundParameters) #Check if Details, MapPoint or MinuteSummeries params are being used. if ($params.Details) { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Activities?activityTypes=$activity&activityIncludes" } elseif ($params.MapPoints) { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Activities?activityTypes=$activity&activityIncludes" } elseif ($params.MinuteSummaries) { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Activities?activityTypes=$activity&activityIncludes" } else { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Activities?activityTypes=$activity" } switch ($params) { { ($_.Details) } { $HttpRequestUrl = $HttpRequestUrl + '=Details' } #fix issue when this is not selected first. { ($_.MapPoints) } { $HttpRequestUrl = $HttpRequestUrl + ',MapPoints' } { ($_.MinuteSummaries) } { $HttpRequestUrl = $HttpRequestUrl + ',MinuteSummaries' } { -not([String]::IsNullOrWhiteSpace($_.StartTime)) } { $HttpRequestUrl = $HttpRequestUrl + "&startTime=$(([datetime]($_.StartTime)).toString('o'))" } { -not([String]::IsNullOrWhiteSpace($_.EndTime)) } { $HttpRequestUrl = $HttpRequestUrl + "&endTime=$(([datetime]($_.Endtime)).toString('o'))" } { -not([String]::IsNullOrWhiteSpace($_.MaxPageSize)) } { $HttpRequestUrl = $HttpRequestUrl + "&maxPageSize=$($_.maxPageSize)" } Default { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Activities?activityTypes=$activity" } } $result = Get-MicrosoftHealthData -RequestUrl $HttpRequestUrl $result.(-join ($activity,'activities')) #PSAvoidInvokingEmptyMembers '($($activity+'Activities'))' has non-constant members scriptanalyzer rule fixed } } # .EXTERNALHELP MicrosoftHealth.psm1-help.xml Function Get-MicrosoftHealthSummary { [CmdletBinding()] [OutputType('System.Management.Automation.PSCustomObject')] [Alias('ghs')] param( [ValidateSet('Daily', 'Hourly')] [Parameter(Mandatory = $true, Position = 0)] [string]$Period, [Parameter(Mandatory = $false)] [Validatepattern('^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$')] [string]$StartTime, [Parameter(Mandatory = $false)] [Validatepattern('^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$')] [string]$EndTime, [Parameter(Mandatory = $false)] [int]$maxPageSize ) process { $params = [pscustomobject]([ordered]@{}+$PSBoundParameters) $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Summaries/$Period"+'?' switch ($params) { { -not([String]::IsNullOrWhiteSpace($_.StartTime)) } { $HttpRequestUrl = $HttpRequestUrl + "startTime=$(([datetime]($_.StartTime)).toString('o'))" } { -not([String]::IsNullOrWhiteSpace($_.EndTime)) } { $HttpRequestUrl = $HttpRequestUrl + "&endTime=$(([datetime]($_.Endtime)).toString('o'))" } { -not([String]::IsNullOrWhiteSpace($_.MaxPageSize)) } { #Check if MaxPageSize is first param. Write-Verbose $HttpRequestUrl if (!($HttpRequestUrl -match '\?$')) #if httprequesturl does not end on ? use & sign { $HttpRequestUrl = $HttpRequestUrl + "&maxPageSize=$($_.maxPageSize)" } elseif ($HttpRequestUrl -match '\?' -and (!($HttpRequestUrl -match '$?'))) #check if ? is used somewhere in httprequest { $HttpRequestUrl = $HttpRequestUrl + "&maxPageSize=$($_.maxPageSize)" } else { $HttpRequestUrl = $HttpRequestUrl + "maxPageSize=$($_.maxPageSize)" } } Default { $HttpRequestUrl = "https://api.microsofthealth.net/v1/me/Summaries/$Period" } } Write-Verbose $HttpRequestUrl $result = Get-MicrosoftHealthData -RequestUrl $HttpRequestUrl $result.summaries } } #region Authentication function Get-AuthenticationSettingsPath { $script:AuthenticationSettingsPath } function Load-AuthenticationSettings { try { $path = Get-AuthenticationSettingsPath Import-Clixml -Path $path } catch [System.IO.FileNotFoundException] { "Follow step 8 of README.md file" notepad "$PSScriptRoot\README.md" } } function Set-AuthenticationToken { param ( $AccessToken, $RefreshToken ) $settings = Load-AuthenticationSettings $AuthObject = New-Object -TypeName psObject -Property @{ ClientId = $settings.ClientId Secret = $settings.Secret AccessToken = $AccessToken RefreshToken = $RefreshToken } #Store AccessToken and RefreshToken in configuration file. $path = Get-AuthenticationSettingsPath Export-Clixml -Path $path -InputObject $AuthObject } #endregion <# #region Unused Functions function New-AuthenticationSettings { param ( $ClientId, $AccessToken ) New-Object -TypeName psObject -Property @{ ClientId = $ClientId AccessToken = $AccessToken } } function Save-AuthenticationSettings { param ( $AuthenticationSettings ) $path = Get-AuthenticationSettingsPath Export-Clixml -Path $path -InputObject $AuthenticationSettings } #endregion #> |