MyTesla.psm1
#Region '.\_PrefixCode.ps1' 0 # Code in here will be prepended to top of the psm1-file. $Script:TeslaConfiguration = @{ 'LastSeen' = [System.DateTimeOffset]::MinValue } $Script:AuthUrl = @{ 'USA' = 'auth.tesla.com' 'China' = 'auth.tesla.cn' } #EndRegion '.\_PrefixCode.ps1' 10 #Region '.\Classes\TeslaSeat.ps1' 0 enum TeslaSeat { Driver = 0 Passenger = 1 BackseatLeft = 2 BackseatCenter = 3 BackseatRight = 4 } #EndRegion '.\Classes\TeslaSeat.ps1' 8 #Region '.\Classes\TeslaVehicle.ps1' 0 Class TeslaVehicle { [System.Int64] $Id [System.Int64] $vehicle_id [System.String] $vin [System.String] $display_name [System.String] $option_codes [System.String] $color [System.String] $access_type [System.String[]] $tokens [System.String] $state [System.Boolean] $in_service [System.String] $id_s [System.Boolean] $calendar_enabled [System.Int64] $api_version [System.Object] $backseat_token [System.Object] $backseat_token_updated_at } #EndRegion '.\Classes\TeslaVehicle.ps1' 18 #Region '.\Private\AuthHelpers.ps1' 0 function Get-RandomString { param ( [Parameter(Mandatory)] [int] $Length ) -join (Get-Random -InputObject 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.ToCharArray() -Count $Length) } function ConvertTo-SHA256Hash { param( [Parameter(Mandatory, ValueFromPipeline)] [string] $String ) process { $Hasher = [System.Security.Cryptography.SHA256]::Create() $HashBytes = $Hasher.ComputeHash([System.Text.Encoding]::Default.GetBytes($String)) $Hash = ConvertTo-Hex -Bytes $HashBytes Write-Output $Hash } } function ConvertTo-Hex { param( [Parameter(ValueFromPipeline)] [byte[]] $Bytes, [switch] $ToUpper ) process { $format = if ($ToUpper.IsPresent) { 'X2' } else { 'x2' } $HexChars = $Bytes | Foreach-Object -MemberName ToString -ArgumentList $format $HexString = -join $HexChars Write-Output $HexString } } function ConvertTo-Base64 { param( [Parameter(Mandatory, ValueFromPipeline)] [string]$String ) process { [convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($String)) } } function ConvertTo-UrlEncodedContent { [CmdletBinding()] [OutputType([String])] param( [hashtable] $Hashtable ) $Dictionary = [System.Collections.Generic.Dictionary[string, string]]::new() foreach ($key in $Hashtable.Keys) { $Dictionary.Add($key, $Hashtable[$key]) } $FormUrlencodedContent = [System.Net.Http.FormUrlEncodedContent]::new($Dictionary) return [System.Text.Encoding]::UTF8.GetString($FormUrlencodedContent.ReadAsByteArrayAsync().Result) } function ConvertTo-QueryString { [CmdletBinding()] [OutputType([String])] param( [System.Collections.Specialized.OrderedDictionary] $Hashtable ) $QueryString = [System.Web.HttpUtility]::ParseQueryString('') foreach ($key in $Hashtable.Keys) { $QueryString.Add($key, $Hashtable[$key]) } return $QueryString.ToString() } function New-LoginSession { param( [validateset('USA', 'China')] [string] $Region = 'USA' ) $LoginSession = @{ 'CodeVerifier' = Get-RandomString -Length 86 'State' = Get-RandomString -Length 20 'Region' = $Region 'BaseUri' = $Script:AuthUrl[$Region] 'UserAgent' = '007' } $LoginSession['CodeChallenge'] = $LoginSession['CodeVerifier'] | ConvertTo-SHA256Hash | ConvertTo-Base64 $Fragment = 'oauth2/v3/authorize' $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment) $Query = [ordered]@{ 'client_id' = 'ownerapi' 'code_challenge' = $LoginSession.CodeChallenge 'code_challenge_method' = 'S256' 'redirect_uri' = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString() 'response_type' = 'code' 'scope' = 'openid email offline_access' 'state' = $LoginSession.State } $Uri.Query = ConvertTo-QueryString -Hashtable $Query $Params = @{ Uri = $Uri.Uri.ToString() Method = 'GET' UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession MaximumRedirection = 0 Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' } } $Response = Invoke-WebRequest @Params -SessionVariable 'WebSession' -ErrorAction 'Stop' $FormFields = @{} [Regex]::Matches($Response.Content, 'type=\"hidden\" name=\"(?<name>.*?)\" value=\"(?<value>.*?)\"') | Foreach-Object { $FormFields.Add($_.Groups['name'].Value, $_.Groups['value'].Value) } $LoginSession.FormFields = $FormFields $LoginSession['WebSession'] = $WebSession return $LoginSession } function Get-TeslaAuthCode { [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingPlainTextForPassword', 'Password', Justification = 'We are sending the password as plain text in body, not really a point to have it in a securestring in this private function.' )] param( [Parameter(Mandatory)] [string] $Username, [Parameter(Mandatory)] [string] $Password, [Parameter()] [string] $MfaCode, [Parameter(Mandatory)] [hashtable] $LoginSession ) $LoginSession.FormFields['identity'] = $Username $LoginSession.FormFields['credential'] = $Password $Fragment = 'oauth2/v3/authorize' $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment) $Query = [ordered]@{ 'client_id' = 'ownerapi' 'code_challenge' = $LoginSession.CodeChallenge 'code_challenge_method' = 'S256' 'redirect_uri' = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString() 'response_type' = 'code' 'scope' = 'openid email offline_access' 'state' = $LoginSession.State } $Uri.Query = ConvertTo-QueryString -Hashtable $Query # Try here to catch HTTP Redirect try { $Body = ConvertTo-UrlEncodedContent $LoginSession.FormFields $Params = @{ Uri = $Uri.Uri.ToString() Method = 'POST' ContentType = 'application/x-www-form-urlencoded' Body = $Body UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession MaximumRedirection = 0 Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' } } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' if ($Response.StatusCode -eq [System.Net.HttpStatusCode]::OK -and $Response.Content.Contains('passcode')) { Write-Verbose -Message 'MFA Requried' $MFARequirements = Get-MFARequirements -LoginSession $LoginSession if (-not [string]::IsNullOrEmpty($MfaCode)) { foreach ($MFAId in $MFARequirements) { if (Submit-MfaCode -MfaId $MfaId.id -MfaCode $MfaCode -LoginSession $LoginSession) { $Code = Get-TeslaAuthCodeMfa -LoginSession $LoginSession return $Code } } } else { throw 'MFA code is required.' # use $MFARequirements here } } else { throw 'Failed to get AuthCode, no redirect.' } } catch [Microsoft.PowerShell.Commands.HttpResponseException] { if ($_.exception.response.StatusCode -eq [System.Net.HttpStatusCode]::Redirect) { Write-Verbose -Message 'Got redirect, parsing Location without MFA' [uri]$Location = $_.exception.response.headers.Location.OriginalString if (-not [string]::IsNullOrEmpty($Location)) { $Code = [System.Web.HttpUtility]::ParseQueryString($Location.Query).Get('Code') if (-not [string]::IsNullOrEmpty($Code)) { return $Code } else { throw 'No auth code received. Please try again later.' } } else { throw 'Redirect location not found.' } } else { throw } } } function Submit-MfaCode { param( [Parameter(Mandatory)] [string] $MfaId, [Parameter(Mandatory)] [string] $MfaCode, [Parameter(Mandatory)] [hashtable] $LoginSession ) $Fragment = 'oauth2/v3/authorize/mfa/verify' $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment) $Params = @{ Uri = $Uri.Uri.ToString() Method = 'POST' ContentType = 'application/json; charset=utf-8' Body = [ordered]@{ 'factor_id' = $MfaId 'passcode' = $MfaCode 'transaction_id' = $LoginSession.FormFields.transaction_id } | ConvertTo-Json -Compress UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' 'Referer' = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $null).Uri.ToString() } } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' $Content = $Response.Content | ConvertFrom-Json $IsValid = [bool]$Content.data.valid return $IsValid } function Get-MFARequirements { param( [Parameter(Mandatory)] [hashtable] $LoginSession ) $Fragment = 'oauth2/v3/authorize/mfa/factors' $Uri = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, $Fragment) $Query = [ordered]@{ 'transaction_id' = $LoginSession.FormFields.transaction_id } $Uri.Query = ConvertTo-QueryString -Hashtable $Query $Params = @{ Uri = $Uri.Uri Method = 'GET' UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession MaximumRedirection = 0 Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' } } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' $Content = $Response.Content | ConvertFrom-Json return $Content.data # foreach ($MfaId in $Content.data) { # if (Test-MfaCode -MfaId $MfaId.id -MfaCode $MfaCode -LoginSession $LoginSession) { # return $true # } # } # # If we get here, MFA was invalid # return $false } function Get-TeslaAuthCodeMfa { param( [Parameter(Mandatory)] [hashtable] $LoginSession ) $Fragment = 'oauth2/v3/authorize' $Uri = [System.UriBuilder]::new('https',$LoginSession.BaseUri,443,$Fragment) $Body = ConvertTo-UrlEncodedContent @{ 'transaction_id' = $LoginSession.FormFields['transaction_id'] } $Query = [ordered]@{ 'client_id' = 'ownerapi' 'code_challenge' = $LoginSession.CodeChallenge 'code_challenge_method' = 'S256' 'redirect_uri' = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString() 'response_type' = 'code' 'scope' = 'openid email offline_access' 'state' = $LoginSession.State } $Uri.Query = ConvertTo-QueryString -Hashtable $Query try { $Params = @{ Uri = $Uri.Uri.ToString() Method = 'POST' ContentType = 'application/x-www-form-urlencoded' Body = $Body UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession MaximumRedirection = 0 Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' } } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' # If we get here, we failed. Write terminating error. Write-Error -Message 'Failed to get AuthCode with MFA, no redirect.' -TargetObject $Response -ErrorAction 'Stop' } catch [Microsoft.PowerShell.Commands.HttpResponseException] { if ($_.exception.response.StatusCode -eq [System.Net.HttpStatusCode]::Redirect) { [uri]$Location = $_.exception.response.headers.Location.OriginalString if (-not [string]::IsNullOrEmpty($Location)) { $Code = [System.Web.HttpUtility]::ParseQueryString($Location.Query).Get('Code') if (-not [string]::IsNullOrEmpty($Code)) { return $Code } else { Write-Error -Message 'No auth code received. Please try again later.' -Exception $_.Exception -TargetObject $_ -ErrorAction 'Stop' } } else { Write-Error -Message 'Redirect location not found.' -Exception $_.Exception -TargetObject $_ -ErrorAction 'Stop' } } } } function Get-TeslaAuthToken { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Code, [Parameter(Mandatory)] [hashtable] $LoginSession ) $Fragment = 'oauth2/v3/token' $Uri = [System.UriBuilder]::new('https',$LoginSession.BaseUri,443,$Fragment) $Params = @{ Uri = $Uri.Uri.ToString() Method = 'POST' Body = [ordered]@{ 'grant_type' = 'authorization_code' 'client_id' = 'ownerapi' 'code' = $Code 'code_verifier' = $LoginSession.CodeVerifier 'redirect_uri' = [System.UriBuilder]::new('https', $LoginSession.BaseUri, 443, 'void/callback').Uri.ToString() } | ConvertTo-Json -Compress ContentType = 'application/json; charset=utf-8' UserAgent = $LoginSession.UserAgent WebSession = $LoginSession.WebSession Headers = @{ 'Accept' = 'application/json' 'Accept-Encoding' = 'gzip, deflate' } } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' $CreationTime = $Response.Headers.Date | Foreach-Object { $_ -as [System.DateTimeOffset] } $Content = $Response.Content | ConvertFrom-Json $Token = @{ AccessToken = $Content.access_token RefreshToken = $Content.refresh_token IdToken = $Content.id_token WhenCreated = $CreationTime WhenExpires = $CreationTime.AddSeconds($Content.expires_in) State = $Content.state Response = $Response } return $Token } function Get-TeslaAccessToken { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $AuthToken ) $Fragment = 'oauth/token' $Uri = [System.UriBuilder]::new('https','owner-api.teslamotors.com',443,$Fragment) $Params = @{ Uri = $Uri.Uri.ToString() Method = 'POST' Body = @{ 'grant_type' = 'urn:ietf:params:oauth:grant-type:jwt-bearer' 'client_id' = '81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384' 'client_secret' = 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3' } | ConvertTo-Json -Compress Headers = @{ 'Authorization' = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $AuthToken) } ContentType = 'application/json; charset=utf-8' UserAgent = '007' } $Response = Invoke-WebRequest @Params -ErrorAction 'Stop' $Content = $Response.Content | ConvertFrom-Json $CreationTime = [System.DateTimeOffset]::FromUnixTimeSeconds($Content.created_at) $Token = @{ AccessToken = $Content.access_token TokenType = $Content.token_type RefreshToken = $Content.refresh_token CreatedAt = $CreationTime ExpiresAt = $CreationTime.AddSeconds($Content.expires_in) } return $Token } #EndRegion '.\Private\AuthHelpers.ps1' 455 #Region '.\Private\ConvertFrom-Timestamp.ps1' 0 function ConvertFrom-Timestamp { [CmdletBinding()] param ( [parameter(Mandatory, ValueFromPipeline)] [double] $Timestamp ) process { [System.DateTimeOffset]::UnixEpoch.AddMilliSeconds($Timestamp).ToLocalTime() } } #EndRegion '.\Private\ConvertFrom-Timestamp.ps1' 13 #Region '.\Private\Get-TeslaToken.ps1' 0 function Get-TeslaToken { [CmdletBinding()] param ( # Type of token [Parameter()] [validateset('Bearer', 'Access', 'Refresh')] [string] $Type, # Auto renew token if it expires in less than this many days, set to 0 to disable autorenewal. [int] [ValidateRange(0, 45)] $RenewalThreshold = 10 ) if ( $RenewalThreshold -gt 0 -and $null -ne $Script:TeslaConfiguration['Token'].WhenExpires -and $Script:TeslaConfiguration['Token'].WhenExpires -lt [System.DateTimeOffset]::Now.AddDays($RenewalThreshold) ) { try { Connect-Tesla -RefreshToken $Script:TeslaConfiguration['Token'].RefreshToken -ErrorAction 'Stop' } catch { throw 'Failed to renew token in cache.' } } if ($null -ne $Script:TeslaConfiguration['Token']) { switch ($Type) { 'Bearer' { if ($null -ne $Script:TeslaConfiguration['Token'].'AccessToken') { return 'Bearer {0}' -f ($Script:TeslaConfiguration['Token'].'AccessToken' | Unprotect-SecureString) } } 'Access' { if ($null -ne $Script:TeslaConfiguration['Token'].'AccessToken') { return $Script:TeslaConfiguration['Token'].'AccessToken' | Unprotect-SecureString } } 'Refresh' { if ($null -ne $Script:TeslaConfiguration['Token'].'RefreshToken') { return $Script:TeslaConfiguration['Token'].'RefreshToken' | Unprotect-SecureString } } Default { throw "Type [$Type] not implemented" } } } throw 'Not signed in, please use Connect-Tesla to sign in to the API' } #EndRegion '.\Private\Get-TeslaToken.ps1' 55 #Region '.\Private\Unprotect-SecureString.ps1' 0 <# .NOTES Code taken from http://blog.majcica.com/2015/11/17/powershell-tips-and-tricks-decoding-securestring/ #> function Unprotect-SecureString { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [securestring] $SecureString ) process { $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString); try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr); } finally { [Runtime.InteropServices.Marshal]::FreeBSTR($bstr); } } } #EndRegion '.\Private\Unprotect-SecureString.ps1' 25 #Region '.\Public\Close-TeslaChargePort.ps1' 0 function Close-TeslaChargePort { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } #Todo: Will this work without waking up the vehicle? $Fragment = "api/1/vehicles/$Id/command/charge_port_door_close" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Close-TeslaChargePort.ps1' 25 #Region '.\Public\Connect-Tesla.ps1' 0 function Connect-Tesla { [CmdletBinding(DefaultParameterSetName = 'Credential')] param ( # Credentials used to connect to Tesla API [Parameter(ParameterSetName = 'Credential', Mandatory)] [pscredential] $Credential, # MFA Code from Authenticator app. Only needed if MFA is enabled on your account. [Parameter(ParameterSetName = 'Credential')] [string] $MFACode, # Refreshtoken used to connect to Tesla API [Parameter(ParameterSetName = 'RefreshToken', Mandatory)] [securestring] $RefreshToken, # Access token used to connect to Tesla API [Parameter(ParameterSetName = 'AccessToken', Mandatory)] [securestring] $AccessToken, # Region to sign in to, can be USA or China (if you are not in China, use USA) [Parameter(ParameterSetName = 'RefreshToken')] [Parameter(ParameterSetName = 'Credential')] [ValidateSet('USA', 'China')] [string] $Region = 'USA', [Parameter(ParameterSetName = 'RefreshToken')] [Parameter(ParameterSetName = 'Credential')] [switch] $PassThru ) $ErrorActionPreference = 'Stop' switch ($pscmdlet.ParameterSetName) { 'Credential' { $Username = $Credential.UserName $Password = $Credential.GetNetworkCredential().Password $LoginSession = New-LoginSession -Region $Region $Code = Get-TeslaAuthCode -Username $Username -Password $Password -MfaCode $MFACode -LoginSession $LoginSession $AuthTokens = Get-TeslaAuthToken -Code $Code -LoginSession $LoginSession $Token = Get-TeslaAccessToken -AuthToken $AuthTokens.AccessToken $Token['AccessToken'] = $Token['AccessToken'] | ConvertTo-SecureString -AsPlainText -Force $Token['RefreshToken'] = $Token['RefreshToken'] | ConvertTo-SecureString -AsPlainText -Force $Token['IdToken'] = $AuthTokens.IdToken } 'RefreshToken' { throw 'Refresh token support not implemented' } 'AccessToken' { $Token = [PSCustomObject]@{ 'AccessToken' = $AccessToken } } Default { throw "Unsupported parameter set name: [$($pscmdlet.ParameterSetName)]" } } $Script:TeslaConfiguration['Token'] = $Token if ($PassThru.IsPresent) { Write-Output $Token } } #EndRegion '.\Public\Connect-Tesla.ps1' 70 #Region '.\Public\Export-TeslaContext.ps1' 0 function Export-TeslaContext { [CmdletBinding()] param ( # Path to where the context should be saved [Parameter()] [String] $Path = "$Env:APPDATA\MyTesla\TeslaContext.json", # Store context as plain text [Parameter()] [switch] $AsPlainText, # Force file to be overwritten, don't warn about plain text export [Parameter()] [switch] $Force ) $ParentPath = Split-Path -Path $Path -Parent if (-not (Test-Path -Path $ParentPath -PathType Container)) { $null = New-Item -Path $ParentPath -ItemType 'Directory' -ErrorAction Stop } $ConfigurationToExport = Get-TeslaContext if ($ConfigurationToExport.ContainsKey('Token')) { $ConfigurationToExport.Token = $ConfigurationToExport.Token.Clone() } if ($null -ne $ConfigurationToExport.Token.AccessToken) { if ($AsPlainText.IsPresent) { $ConfigurationToExport.Token.AccessToken = Unprotect-SecureString -SecureString $ConfigurationToExport.Token.AccessToken } else { $ConfigurationToExport.Token.AccessToken = $ConfigurationToExport.Token.AccessToken | ConvertFrom-Securestring } } if ($null -ne $ConfigurationToExport.Token.RefreshToken) { if ($AsPlainText.IsPresent) { $ConfigurationToExport.Token.RefreshToken = Unprotect-SecureString -SecureString $ConfigurationToExport.Token.RefreshToken } else { $ConfigurationToExport.Token.RefreshToken = $ConfigurationToExport.Token.RefreshToken | ConvertFrom-Securestring } } $JsonConfiguration = $ConfigurationToExport | ConvertTo-Json -Compress if (-not $AsPlainText.IsPresent) { $JsonConfiguration = ConvertTo-SecureString -String $JsonConfiguration -AsPlainText -Force | ConvertFrom-Securestring } $Params = @{ Path = $Path Value = $JsonConfiguration Encoding = 'utf8' } if ($Force.IsPresent) { $Params['Force'] = $Force } Set-Content @Params } #EndRegion '.\Public\Export-TeslaContext.ps1' 64 #Region '.\Public\Get-TeslaChargeState.ps1' 0 function Get-TeslaChargeState { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/charge_state" Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaChargeState.ps1' 22 #Region '.\Public\Get-TeslaClimateState.ps1' 0 function Get-TeslaClimateState { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/climate_state" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaClimateState.ps1' 24 #Region '.\Public\Get-TeslaContext.ps1' 0 function Get-TeslaContext { [CmdletBinding()] param ( [switch] $Force ) $Script:TeslaConfiguration.Clone() } #EndRegion '.\Public\Get-TeslaContext.ps1' 11 #Region '.\Public\Get-TeslaDriveState.ps1' 0 function Get-TeslaDriveState { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/drive_state" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaDriveState.ps1' 24 #Region '.\Public\Get-TeslaGUISettings.ps1' 0 function Get-TeslaGUISettings { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id') -and $Script:TeslaConfiguration.ContainsKey('CurrentVehicleId')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/gui_settings" Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaGUISettings.ps1' 23 #Region '.\Public\Get-TeslaVehicle.ps1' 0 function Get-TeslaVehicle { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) $Fragment = 'api/1/vehicles' if ($PSBoundParameters.ContainsKey('Id')) { $Fragment += "/$Id" } Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaVehicle.ps1' 19 #Region '.\Public\Get-TeslaVehicleConfig.ps1' 0 function Get-TeslaVehicleConfig { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/vehicle_config" Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaVehicleConfig.ps1' 25 #Region '.\Public\Get-TeslaVehicleData.ps1' 0 function Get-TeslaVehicleData { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/vehicle_data" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaVehicleData.ps1' 25 #Region '.\Public\Get-TeslaVehicleState.ps1' 0 function Get-TeslaVehicleState { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/data_request/vehicle_state" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'GET' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Get-TeslaVehicleState.ps1' 24 #Region '.\Public\Import-TeslaContext.ps1' 0 function Import-TeslaContext { [CmdletBinding()] param ( # Path to exported Tesla context [Parameter()] [string] $Path = "$Env:APPDATA\MyTesla\TeslaContext.json" ) if (-not (Test-Path -Path $Path -PathType Leaf)) { throw "File not found: $Path" } $ContextContent = Get-Content -Path $Path -Encoding 'utf8' try { # Try reading the context as plain json $TeslaContext = $ContextContent | ConvertFrom-Json -AsHashtable -ErrorAction 'Stop' try { $TeslaContext.Token.AccessToken = $TeslaContext.Token.AccessToken | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'stop' $TeslaContext.Token.RefreshToken = $TeslaContext.Token.RefreshToken | ConvertTo-SecureString -AsPlainText -Force -ErrorAction 'stop' } catch { # Ignore errors here } } catch { # Not valid plain json, try decrypting $TeslaContext = $ContextContent | ConvertTo-SecureString -ErrorAction 'Stop' | Unprotect-SecureString -ErrorAction 'Stop' | ConvertFrom-Json -AsHashtable -ErrorAction 'Stop' $TeslaContext.Token.AccessToken = $TeslaContext.Token.AccessToken | ConvertTo-SecureString $TeslaContext.Token.RefreshToken = $TeslaContext.Token.RefreshToken | ConvertTo-SecureString } $Script:TeslaConfiguration = $TeslaContext $Script:TeslaConfiguration['LastSeen'] = [System.DateTimeOffset]::Now } #EndRegion '.\Public\Import-TeslaContext.ps1' 41 #Region '.\Public\Invoke-TeslaAPI.ps1' 0 function Invoke-TeslaAPI { [CmdletBinding()] param ( [string] $Fragment, [string] $Method, [object] $Body, [switch] $Auth, # Calls Resume-TeslaVehicle to wake the car up before involing API. Will not wake up if we called the API within the last minutes. [switch] $WakeUp, # Parameter help description [Parameter(DontShow)] [string] $BaseUri = 'https://owner-api.teslamotors.com' ) $Fragment = $Fragment -replace '^/+|/+$' $BaseUri = $BaseUri -replace '^/+|/+$' if ($WakeUp.IsPresent) { $Now = [System.DateTimeOffset]::Now $LastSeenSince = ($Now - $Script:TeslaConfiguration['LastSeen']).TotalMinutes if ($LastSeenSince -gt 1) { $Vehicle = Get-TeslaVehicle switch ($Vehicle.state) { 'asleep' { Write-Verbose -Message 'Waking up car...' $ResumeParams = @{ Wait = $true } if ($Fragment -match '^api\/1\/vehicles\/(\d+)') { $ResumeParams['Id'] = $Matches[1] } $null = Resume-TeslaVehicle @ResumeParams Write-Verbose -Message 'Car is woken up' break } 'online' { Write-Verbose -Message 'Car is online' break } Default { throw "Unknown state: $($Vehicle.state)" } } } else { Write-Verbose -Message "Car seen $LastSeenSince minutes ago ($($Script:TeslaConfiguration['LastSeen'])), no need to wake up" } } $Params = @{ Uri = '{0}/{1}' -f $BaseUri, $Fragment Method = $Method Headers = @{ 'Content-Type' = 'application/json' } } if ($PSBoundParameters.ContainsKey('Body')) { if ($Body -is [hashtable]) { $Params['Body'] = $Body | ConvertTo-Json } elseif ($Body -is [string]) { $Params['Body'] = $Body } else { throw "Type $($Body.GetType().Name) is not supported as Body parameter." } } if ($Auth.IsPresent) { $Token = Get-TeslaToken -Type 'Bearer' $Params['Headers']['Authorization'] = $Token } Invoke-RestMethod @Params -ErrorAction 'Stop' $Script:TeslaConfiguration['LastSeen'] = [System.DateTimeOffset]::Now } #EndRegion '.\Public\Invoke-TeslaAPI.ps1' 90 #Region '.\Public\Invoke-TeslaHorn.ps1' 0 function Invoke-TeslaHorn { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/honk_horn" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Invoke-TeslaHorn.ps1' 23 #Region '.\Public\Invoke-TeslaLightFlash.ps1' 0 function Invoke-TeslaLightFlash { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/flash_lights" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Invoke-TeslaLightFlash.ps1' 23 #Region '.\Public\Lock-TeslaDoor.ps1' 0 function Lock-TeslaDoor { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/door_lock" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Lock-TeslaDoor.ps1' 24 #Region '.\Public\Move-TeslaSunroof.ps1' 0 function Move-TeslaSunroof { [CmdletBinding(ConfirmImpact = 'Low')] param ( # Id of Tesla Vehicle [Parameter(ParameterSetName = '__AllParameterSets')] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [Parmameter(Mandatory, ParameterSetName = 'State')] [ValidateSet('Open', 'Closed', 'Comfort', 'Vent')] [string] $State, [Parmameter(Mandatory, ParameterSetName = 'Percent')] [ValidateRange(0, 100)] [int] $Percent ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } switch ($PSCmdlet.ParameterSetName) { 'Percent' { $Body = @{ State = 'move' Percent = $Percent } break } 'State' { $Body = @{ State = $State.ToLower() } break } Default { throw 'Unknown parameter set' } } $Fragment = "api/1/vehicles/$Id/command/sun_roof_control" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Move-TeslaSunroof.ps1' 55 #Region '.\Public\Open-TeslaChargePort.ps1' 0 function Open-TeslaChargePort { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } #Todo: Will this work without waking up the vehicle? $Fragment = "api/1/vehicles/$Id/command/charge_port_door_open" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Open-TeslaChargePort.ps1' 25 #Region '.\Public\Open-TeslaTrunk.ps1' 0 function Open-TeslaTrunk { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Select to upen front or rear trunk. Defaults to front (frunk) [ValidateSet('front', 'rear')] $WhichTrunk = 'front' ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/actuate_trunk" $Body = @{ which_trunk = $WhichTrunk } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Open-TeslaTrunk.ps1' 30 #Region '.\Public\Reset-TeslaValetModePIN.ps1' 0 function Reset-TeslaValetModePIN { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/reset_valet_pin" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Reset-TeslaValetModePIN.ps1' 24 #Region '.\Public\Resume-TeslaVehicle.ps1' 0 # Uncertain of verb choice here. # Could be any of # Initialize # Restore # Enable # Resume function Resume-TeslaVehicle { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [switch] $Wait, [Parameter(DontShow)] [int] $MaxNumberOfTries = 10 ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/wake_up" Write-Verbose -Message "Waking up Tesla with Id: $Id..." $Response = Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response if ($Wait.IsPresent) { $NumberOfTries = 1 $SleepTime = 2 $SleepAddition = 2 while ($Response.state -ne 'online' -and $NumberOfTries -le $MaxNumberOfTries) { Write-Verbose -Message "Waiting for car to go online..." Start-Sleep -Seconds $SleepTime $SleepAddition++ $SleepTime += $SleepAddition $Response = Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } } Write-Output $Response } #EndRegion '.\Public\Resume-TeslaVehicle.ps1' 55 #Region '.\Public\Revoke-TeslaToken.ps1' 0 function Revoke-TeslaToken { [CmdletBinding()] param ( # Token to revoke [Parameter()] [securestring] $Token ) $Body = @{ token = Unprotect-SecureString -SecureString $Token } $Fragment = "oauth/revoke" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body | Select-Object -ExpandProperty response } #EndRegion '.\Public\Revoke-TeslaToken.ps1' 17 #Region '.\Public\Select-TeslaVehicle.ps1' 0 function Select-TeslaVehicle { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) $Script:TeslaConfiguration['CurrentVehicleId'] = $Id } #EndRegion '.\Public\Select-TeslaVehicle.ps1' 15 #Region '.\Public\Set-TeslaChargeLimit.ps1' 0 function Set-TeslaChargeLimit { [CmdletBinding(DefaultParameterSetName='Standard')] param ( # Set charge limit to given value in percent [Parameter(Mandatory, ParameterSetName='Limit')] [ValidateRange(50,100)] [Int] $LimitPercent, # Set charge limit to standard (90%) [Parameter(Mandatory, ParameterSetName='Standard')] [Switch] $Standard, # Set charge limit to max (100%) [Parameter(Mandatory, ParameterSetName='Max')] [Switch] $Max, # Id of Tesla Vehicle [Parameter(ParameterSetName='__AllParameterSets')] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Params = @{} switch ($PSCmdlet.ParameterSetName) { 'Limit' { $Params['Fragment'] = "api/1/vehicles/$Id/command/set_charge_limit" $Params['Body'] = @{ percent = $LimitPercent } } 'Standard' { $Params['Fragment'] = "api/1/vehicles/$Id/command/charge_standard" } 'Max' { $Params['Fragment'] = "api/1/vehicles/$Id/command/charge_max_range" } Default { throw 'This should not have happend.'} } $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI @Params -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaChargeLimit.ps1' 59 #Region '.\Public\Set-TeslaDefrost.ps1' 0 function Set-TeslaDefrost { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [Parameter(Mandatory)] [bool] $State ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/set_preconditioning_max" $Body = @{ on = $State } Invoke-TeslaAPI -Fragment $Fragment -Body $Body -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaDefrost.ps1' 32 #Region '.\Public\Set-TeslaMediaFavourite.ps1' 0 # This verb could be Skip, Switch, Step or Set function Set-TeslaMediaFavourite { [CmdletBinding(DefaultParameterSetName='Next')] param ( # Id of Tesla Vehicle [Parameter(ParameterSetName='Next')] [Parameter(ParameterSetName='Prev')] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Change to next favourite radio channel [Parameter(ParameterSetName='Next')] [switch] $Next, # Change to previous favourite radio channel [Parameter(ParameterSetName='Prev')] [switch] $Previous ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } switch ($PSCmdlet.ParameterSetName) { 'Next' { $Fragment = "api/1/vehicles/$Id/command/media_next_fav" break } 'Prev' { $Fragment = "api/1/vehicles/$Id/command/media_prev_fav" break } Default { throw 'Unexpected parameter set name.' } } Write-Verbose -Message "Changing to $($PSCmdlet.ParameterSetName) favourite" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaMediaFavourite.ps1' 49 #Region '.\Public\Set-TeslaMediaTrack.ps1' 0 # This verb could be Skip, Switch, Step or Set function Set-TeslaMediaTrack { [CmdletBinding(DefaultParameterSetName='Next')] param ( # Id of Tesla Vehicle [Parameter(ParameterSetName='Next')] [Parameter(ParameterSetName='Prev')] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Play next track [Parameter(ParameterSetName='Next')] [switch] $Next, # Play Previous track [Parameter(ParameterSetName='Prev')] [switch] $Previous ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } switch ($PSCmdlet.ParameterSetName) { 'Next' { $Fragment = "api/1/vehicles/$Id/command/media_next_track" break } 'Prev' { $Fragment = "api/1/vehicles/$Id/command/media_prev_track" break } Default { throw 'Unexpected parameter set name.' } } Write-Verbose -Message "Changing to $($PSCmdlet.ParameterSetName) track" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaMediaTrack.ps1' 49 #Region '.\Public\Set-TeslaMediaVolume.ps1' 0 function Set-TeslaMediaVolume { [CmdletBinding(DefaultParameterSetName='Up')] param ( # Id of Tesla Vehicle [Parameter(ParameterSetName='Up')] [Parameter(ParameterSetName='Down')] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Turn volume up [Parameter(ParameterSetName='Up')] [switch] $Up, # Turn volume down [Parameter(ParameterSetName='Down')] [switch] $Down ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } switch ($PSCmdlet.ParameterSetName) { 'Up' { $Fragment = "api/1/vehicles/$Id/command/media_volume_up" break } 'Down' { $Fragment = "api/1/vehicles/$Id/command/media_volume_down" break } Default { throw 'Unexpected parameter set name.' } } Write-Verbose -Message "Turning volume $($PSCmdlet.ParameterSetName)" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaMediaVolume.ps1' 48 #Region '.\Public\Set-TeslaNavigation.ps1' 0 function Set-TeslaNavigation { [CmdletBinding()] param ( # Id of Tesla Vehicle [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Destination to share [string] $Destination ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } [long]$Timestamp = [System.DateTimeOffset]::now - [System.DateTimeOffset]::UnixEpoch | Select-Object -ExpandProperty TotalMilliseconds $Body = @{ "type" = "share_ext_content_raw" "locale" = "en-US" "value" = @{ "android.intent.extra.TEXT" = $Destination } "timestamp_ms" = $Timestamp } $Fragment = "/api/1/vehicles/${id}/command/share" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaNavigation.ps1' 37 #Region '.\Public\Set-TeslaSeatHeater.ps1' 0 function Set-TeslaSeatHeater { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [Parameter(Mandatory)] [TeslaSeat] $Seat, [Parameter(Mandatory)] [ValidateRange(0,3)] [ValidateSet(0,1,2,3)] [int] $Level ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/remote_seat_heater_request" $Body = @{ heater = $Seat.value__ level = $Level } Invoke-TeslaAPI -Fragment $Fragment -Body $Body -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaSeatHeater.ps1' 39 #Region '.\Public\Set-TeslaTemperature.ps1' 0 function Set-TeslaTemperature { [CmdletBinding(DefaultParameterSetName = 'SetTemp')] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11, 200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [Parameter(Mandatory, ParameterSetName = 'SetTemp')] [ValidateRange(15.5, 27.5)] [float] $Temperature, [Parameter(Mandatory, ParameterSetName = 'High')] [Alias('Max')] [Switch] $High, [Parameter(Mandatory, ParameterSetName = 'Low')] [Alias('Min')] [Switch] $Low ) if (-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } switch ($PScmdlet.ParameterSetName) { 'SetTemp' { $SetTemperature = '{0:N1}' -f $Temperature } 'High' { $SetTemperature = '{0:N1}' -f 28 } 'Low' { $SetTemperature = '{0:N1}' -f 15 } Default { throw "Invalid parameter set: $($PScmdlet.ParameterSetName)" } } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/set_temps" $Body = @{ driver_temp = $SetTemperature passenger_temp = $SetTemperature } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Set-TeslaTemperature.ps1' 59 #Region '.\Public\Start-TeslaCharging.ps1' 0 function Start-TeslaCharging { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/charge_start" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaCharging.ps1' 26 #Region '.\Public\Start-TeslaClimate.ps1' 0 function Start-TeslaClimate { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/auto_conditioning_start" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaClimate.ps1' 26 #Region '.\Public\Start-TeslaDefrost.ps1' 0 function Start-TeslaDefrost { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait Set-TeslaDefrost -Id $Id -State $true } #EndRegion '.\Public\Start-TeslaDefrost.ps1' 24 #Region '.\Public\Start-TeslaSentryMode.ps1' 0 function Start-TeslaSentryMode { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/set_sentry_mode" $Body = @{ 'on' = $true } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaSentryMode.ps1' 26 #Region '.\Public\Start-TeslaSteeringWheelHeater.ps1' 0 function Stop-TeslaSteeringWheelHeater { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/remote_steering_wheel_heater_request" $Body = @{ 'on' = $false } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaSteeringWheelHeater.ps1' 26 #Region '.\Public\Start-TeslaUpdate.ps1' 0 function Start-TeslaUpdate { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Number of minutes before the updating starts, use 0 for immediate install [Parameter()] [int] $Minutes = 10 ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "/api/1/vehicles/${id}/command/schedule_software_update" $Body = @{ offset_sec = $Minutes * 60 } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaUpdate.ps1' 31 #Region '.\Public\Start-TeslaValetMode.ps1' 0 function Start-TeslaValetMode { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Optional four digit PIN code used to stop valet mode. [Parameter()] [ValidateScript({if($_ -match '^\d{4}$'){return $true}else{throw 'Invalid PIN, need to be exact four digits.'}})] [string] $PIN ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/set_valet_mode" $Body = @{ on = $true } if($PSBoundParameters.ContainsKey('PIN')) { $Body['password'] = $PIN } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp -Body $Body | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaValetMode.ps1' 37 #Region '.\Public\Start-TeslaVehicle.ps1' 0 function Start-TeslaVehicle { [CmdletBinding(ConfirmImpact='Low')] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, [Parmameter(Mandatory)] [securestring] $Password ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/remote_start_drive" $Body = @{ password = Unprotect-SecureString -SecureString $Password } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Start-TeslaVehicle.ps1' 31 #Region '.\Public\Stop-TeslaCharging.ps1' 0 function Stop-TeslaCharging { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/charge_stop" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Stop-TeslaCharging.ps1' 26 #Region '.\Public\Stop-TeslaClimate.ps1' 0 function Stop-TeslaClimate { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait $Fragment = "api/1/vehicles/$Id/command/auto_conditioning_stop" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Stop-TeslaClimate.ps1' 26 #Region '.\Public\Stop-TeslaDefrost.ps1' 0 function Stop-TeslaDefrost { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $null = Resume-TeslaVehicle -Id $Id -Wait Set-TeslaDefrost -Id $Id -State $false } #EndRegion '.\Public\Stop-TeslaDefrost.ps1' 24 #Region '.\Public\Stop-TeslaSentryMode.ps1' 0 function Stop-TeslaSentryMode { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/set_sentry_mode" $Body = @{ 'on' = $false } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Stop-TeslaSentryMode.ps1' 26 #Region '.\Public\Stop-TeslaSteeringWheelHeater.ps1' 0 function Start-TeslaSteeringWheelHeater { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/remote_steering_wheel_heater_request" $Body = @{ 'on' = $true } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Body $Body -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Stop-TeslaSteeringWheelHeater.ps1' 26 #Region '.\Public\Stop-TeslaValetMode.ps1' 0 function Stop-TeslaValetMode { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id, # Optional four digit PIN code used to stop valet mode. [Parameter()] [ValidatePattern('^\d{4}$', ErrorMessage = '{0} is not a valid PIN. Needs to be exact four digits.')] [string] $PIN ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/set_valet_mode" $Body = @{ on = $false } if($PSBoundParameters.ContainsKey('PIN')) { $Body['password'] = $PIN } Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp -Body $Body | Select-Object -ExpandProperty response } #EndRegion '.\Public\Stop-TeslaValetMode.ps1' 37 #Region '.\Public\Suspend-TeslaUpdate.ps1' 0 function Suspend-TeslaUpdate { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "/api/1/vehicles/${id}/command/cancel_software_update" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Suspend-TeslaUpdate.ps1' 23 #Region '.\Public\Switch-TeslaMediaPlayback.ps1' 0 function Switch-TeslaMediaPlayback { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/media_toggle_playback" Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth -WakeUp | Select-Object -ExpandProperty response } #EndRegion '.\Public\Switch-TeslaMediaPlayback.ps1' 23 #Region '.\Public\Unlock-TeslaDoor.ps1' 0 function Unlock-TeslaDoor { [CmdletBinding()] param ( # Id of Tesla Vehicle [Parameter()] [ValidateLength(11,200)] [ValidatePattern('\d+', ErrorMessage = '{0} is not a valid vehicle ID.')] [string] $Id ) if(-not $PSBoundParameters.ContainsKey('Id')) { $Id = $Script:TeslaConfiguration['CurrentVehicleId'] } if([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Vehicle Id, use the parameter Id or set a default Id using Select-TeslaVehicle' } $Fragment = "api/1/vehicles/$Id/command/door_unlock" $null = Resume-TeslaVehicle -Id $Id -Wait Invoke-TeslaAPI -Fragment $Fragment -Method 'POST' -Auth | Select-Object -ExpandProperty response } #EndRegion '.\Public\Unlock-TeslaDoor.ps1' 24 #Region '.\Public\Wait-TeslaUserPresent.ps1' 0 function Wait-TeslaUserPresent { param ( [Parameter(Mandatory)] [int] $TimeoutSeconds, [int] $Interval = 30 ) $TotalTime = 0 while(($TotalTime -lt $TimeoutSeconds) -and (-not (Get-TeslaVehicleState).is_user_present)) { Start-Sleep -Seconds $Interval $TotalTime += $Interval } } #EndRegion '.\Public\Wait-TeslaUserPresent.ps1' 17 |