Withings-Sync Python/Powershell Script w/ Discord Webhook Notifications

I recently deployed https://github.com/jaroslawhartman/withings-sync to sync my Withings scale to my Garmin account. Here’s a quick Powershell script I whipped up that runs via Scheduled Tasks on Windows and uses a Discord Webhook to notify you of its progress:

# function adapted from https://stackoverflow.com/posts/42995301/revisions
Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
  Try {
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
    }
    $p.WaitForExit()
  }
  Catch {
     exit
  }
}

# call this with garmin username & password to sync from withings
function withings-sync( $username, $password )
{
	# setup vars 
	$hoursToPause = 2
	$fromDate = Get-Date
	$loop = $true
	$countOfTries = 0
	$maxTries = 10
	$user = $username
	$pwd = $password
	$wsPath = "C:\python38\scripts\withings-sync.exe"

	# discord setup
	$discordBot = "Withings -> Garmin Sync"
	$hookUrl = "https://discord.com/api/webhooks/xxxx"
	$content = "{0:HH:mm:ss}: Garmin upload beginning for $user." -f ( (Get-Date) )
	$section = New-DiscordSection -Title "Info" -Description $content -Color LightGrey

	# send discord update on start
	Write-Host $content
	Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot

	do
	{
		# only retry $maxTries times
		if($countOfTries -lt $maxTries)
		{
			# create args, then try withings-sync upload and capture output
			$args = ("--garmin-username ${user} --garmin-password ${pwd} --fromdate {0:yyyy-MM-dd}" -f $fromDate).ToString()			
			$process = Execute-Command -commandTitle "Withings->Garmin Sync" -commandPath $wsPath -commandArguments $args
			$outputText = $process.stdout
			Write-Host $outputText

			# if upload worked, break loops
			if( $outputText.Contains("Fit file uploaded to Garmin Connect") )
			{
				$content = "{0:HH:mm:ss}: Garmin upload successful for $user." -f ( (Get-Date) )
				$section = New-DiscordSection -Title "Info" -Description $content -Color Green
				$loop = $false
			}
			# if no measurements, wait and try again later
			elseif( $outputText.Contains("No measurements to upload for date or period specified") )
			{
				$content = "{0:HH:mm:ss}: No Withings measurements found for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) )
				$section = New-DiscordSection -Title "Alert" -Description $content -Color Yellow
			}
			# if withings refresh failed
			elseif( $outputText.Contains("withings - ERROR") )
			{
				$content = "{0:HH:mm:ss}: Withings refresh failed for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) )
				$section = New-DiscordSection -Title "Alert" -Description $content -Color Red
			}
			# if garmin upload failed
			else
			{
				$content = "{0:HH:mm:ss}: Garmin upload failed for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) )
				$section = New-DiscordSection -Title "Alert" -Description $content -Color Red	
			}
			
			# send discord update
			Write-Host $content
			Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot
			
			# pause for $countOfTries hour(s) and try again
			$countOfTries = $countOfTries + 1
			if($loop) { Start-Sleep ( $hoursToPause * 60 * 60 ) }
		}
		# after #maxTries failures, send discord update and exit the loop
		else
		{
			$content = "{0:HH:mm:ss}: Garmin upload failed $maxTries times, aborting." -f ( (Get-Date) )
			$section = New-DiscordSection -Title "Alert" -Description $content -Color Red 
			Write-Host $content
			Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot
			
			$loop = $false	
		}
	}
	while ($loop -eq $true)
}

Install the PSDiscord powershell module. Set $hookUrl with your Discord Webhook URL. Setup your path to the Python withings-sync executable in the $wsPath variable, then call the withings-sync function from Powershell after initializing your Withings token with withings-sync manually, as per the github instructions (pass your Garmin credentials as the $username and $password parameters). The script will auto-retry up to $maxTries times every $hoursToPause.

Edited 2021-09-21: removed some code from the Execute-Command function that could cause a deadlock when Garmin spits out its 403 error and changed the order of the if/else statements slightly. Cheers!

More Regular Updates

Hi again!

As part of an assignment for the Master of Arts degree I’m currently pursuing at the Academy of Art University in San Francisco, I took a look around this site and did some mild clean-up. Going forward, I’ll be adding new pages as I complete projects that will improve my portfolio. I’m also going to endeavor to start posting here a few times a week, talking about what I’m working on or currently playing–plus I’m sure some random musings or venting.

See you soon!

onewinter network launched

Hello there!

This will be the new incarnation of the onewinter network, the place where I offer my 3d printing services and list the mobile games that I’ve created.

This blog will hopefully be updated with things I learn while working in Unity3d and with my two 3D printers in the future, so that others using the same technology solutions can learn from my struggles and successes.

More to come soon!