Getting Deeper into The Wheel of Time

I finished reading The Eye of the World yesterday. I liked it a lot, and got through it fairly quickly, considering the length of the book and how slowly I usually read. Here’s my Goodreads review.  I noticed yesterday that the books has almost half a million ratings and 20,000 reviews, so I’m pretty sure no one is ever going to stumble across mine on the site, so I might as well link it here.

Per my last post on WoT, this series has activated that part of my brain that likes to go down rabbit holes researching a thing, shopping for stuff, and spending more time on that than actually reading the books. But I did finish the first book, so I’m patting myself on the back for that! Meanwhile, Amazon sent me $5 off coupons for most of the series, so I went ahead and bought the Kindle versions of books one through eleven. I find it hard to imagine myself actually reading my way that far into the series, but, well, I’ve got them in my “official” Kindle library now, at least.

I also paid $7.50 for the “Audible narration” add-on for the first book, in case I want to re-read it at some point, in audiobook format. I assumed that would get me the newer Rosamund Pike version, but instead it got me the older Kate Reading and Michael Kramer version. (Which is fine, since that one is also supposed to be pretty good. Though Pike won an Audie for her version, so maybe I should pick up that version too, and listen to them both… You see how quickly I spiral out of control with these things?)

I may start reading the second book, The Great Hunt, today, since it looks to be a quiet Sunday, and I have nothing much else to do. And I’m interested to see where the story goes. As I’ve said before, I expect that I’ll lose interest in this stuff at some point, but I’m not there yet.

As I read the first book, I followed along with the Reading The Wheel of Time blog post series at tor.com. I’ll likely continue that habit with the next book. It’s fun to connect with other people’s opinions and enthusiasm for a series like this, and there’s quite a community around WoT.

And, as usual with these things, I’ve gotten side-tracked on a couple of fiddly little technical things. First, I started reading Eye of the World from the EPUB file that I got from Tor a long time ago (as mentioned in the previous post). But then I bought the “official” Kindle store version, and switched to that, at about the halfway point in the book. That got me thinking about whether or not I could transfer my highlights from the EPUB to the Kindle store version. Short answer: probably not. But I might hook my Kindle up to my PC via USB today and see if I can copy all of the highlights into a text file and then stick them in Evernote, just for yuks. I could go off on a tangent here about a couple of services I found that automate (or semi-automate) pulling your notes & highlights from the Kindle into other systems, but I’m going to avoid going down that hole right now.

Second, I’ve been thinking about better ways to deal with the tor.com blog post series. The first book had twenty posts, usually covering 2 or 3 chapters each. To read them, I was simply keeping a tab open in Firefox on my MacBook, and never closing Firefox. (That’s unusual for me. I always close Firefox when I’m done, and I don’t have it set to reopen previous tabs on launch.) Then, I would just go back and forth from the series page into the individual posts. And I was keeping a note in Drafts to keep track of how far I had to read in the book before reading the next post. So it was a workable system, but a little weird.

For the next book, I was thinking I could open all of the articles, send them all to Instapaper, in a new folder, and then read them from Instapaper, deleting them as I finished them. If I did that, I’d probably read them on my iPad, so I’d be switching back and forth between the Kindle and iPad. Or, there’s an RSS feed on the series page, so I could subscribe to that in my RSS service, The Old Reader, and then read them from Reeder on my iPad. Or, I could put the RSS feed into Calibre and send the articles from there directly to my Kindle. Or… there are a bunch of things I could screw around with here. You see where things start spiraling out of control for me when I go down these rabbit holes… But I guess it keeps me out of trouble, mostly.

getting authentication tokens from MSAL via PowerShell

I have a little PowerShell script that I can use to get tokens from MSAL, for an API project I maintain, and I could have sworn that I’d blogged about it at some point. But I can’t find a post mentioning it. So I guess it’s one of those things I meant to blog about, but never got around to it.

I just rewrote it for a new API project, so I thought I’d blog about that. And since I never actually blogged about the first version, I might as well include that too.

So the first API is an older .NET Framework project. In the Visual Studio solution, I have both the API and a console program that can be used to run some simple tests against it. The console program, of course, uses MSAL.NET to authenticate. (I blogged about that in 2021.) I also like to do little ad-hoc tests of the API with Fiddler, using the Composer tab. But I need to get a bearer token to do that. There are a bunch of ways to do that, but I wanted a simple PowerShell script that I could run at the command line and that would automatically save the token to the clipboard, so I could paste it into Fiddler. I also wanted the PowerShell script to read the client ID and secret (and other parameters) from the same config file that was used for the console program. The script shown below does that, reading parameters from the console program’s app.config file, and pulling the actual client ID and secret from environment variables. (All of this is, of course, to avoid storing secrets in any text files that might get accidentally checked in to source control…)

# get-auth-hdr-0.ps1
# https://gist.github.com/andyhuey/68bade6eceaff64454eaeabae2351552
# Get the auth hdr and send it to the clipboard.
# ajh 2022-08-29: rewrite to use MSAL.PS.
# ajh 2022-11-23: read secret from env vars.

#Requires -Version 5.1
#Requires -Modules @{ ModuleName="MSAL.PS"; ModuleVersion="4.0" }

# force TLS 1.2
$TLS12Protocol = [System.Net.SecurityProtocolType] 'Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $TLS12Protocol

echo $null | clip	# clear the clipboard.

# read the settings file.
$configFilePath = ".\App.config"
[xml]$configXML = Get-Content $configFilePath
$configXML.configuration.appSettings.add | foreach {
	$add = $_
	switch($add.key) {
		"ida:Authority" 		{$authority = $add.value; break}
		"xyz:ServiceResourceId"	{$svcResourceId = $add.value; break}
		"env:ClientId"			{$client_id_var = $add.value; break}
		"env:ClientSecret" 		{$client_secret_var = $add.value; break}
	}
}
if (!$client_id_var -or !$client_secret_var -or !$authority -or !$svcResourceId) {
	Write-Error "One or more settings are missing from $configFilePath."
	return
}

# and the env vars.
$client_id = [Environment]::GetEnvironmentVariable($client_id_var, 'Machine')
$client_secret = [Environment]::GetEnvironmentVariable($client_secret_var, 'Machine')
if (!$client_id -or !$client_secret) {
	Write-Error "One or more env vars are missing."
	return
}

$scope = $svcResourceId + "/.default"
$secSecret = ConvertTo-SecureString $client_secret -AsPlainText -Force

$msalToken = Get-MsalToken -ClientId $client_id -ClientSecret $secSecret -Scope $scope -Authority $authority
$authHdr = $msalToken.CreateAuthorizationHeader()
$fullAuthHdr = "Authorization: $($authHdr)"
$fullAuthHdr | clip
"auth header has been copied to the clipboard."

For my new project, I needed to create a new version of this script, since the new project is in .NET Core, using an appsettings.json file rather than the old XML format app.config file. I’m also now using the Secret Manager to store the client ID and secret.

# get-auth-hdr-1.ps1
# https://gist.github.com/andyhuey/de85972ec0f6268034e5ce46b0278a07
# Get the auth hdr and send it to the clipboard.
# ajh 2023-04-06: new. 

#Requires -Version 7
#Requires -Modules @{ ModuleName="MSAL.PS"; ModuleVersion="4.0" }

# force TLS 1.2
$TLS12Protocol = [System.Net.SecurityProtocolType] 'Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $TLS12Protocol

echo $null | clip	# clear the clipboard.

$secrets = dotnet user-secrets list --json | ConvertFrom-Json
$clientId = $secrets.'AuthConfig:ClientId'
$clientSecret = $secrets.'AuthConfig:ClientSecret'
$secSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force

$appSettings = Get-Content appsettings.json | ConvertFrom-Json
$scope = $appSettings.AuthConfig.ResourceId
$authority = $appSettings.AuthConfig.Instance -f $appSettings.AuthConfig.TenantId

$msalToken = Get-MsalToken -ClientId $clientId -ClientSecret $secSecret -Scope $scope -Authority $authority
$authHdr = $msalToken.CreateAuthorizationHeader()
$fullAuthHdr = "Authorization: $($authHdr)"
$fullAuthHdr | clip
"auth header has been copied to the clipboard."

So this one is calling “dotnet user-secrets list” to get the secrets. And it’s using “ConvertFrom-Json” for both that and the appsecrets.json file.

Both scripts are using MSAL.PS for the MSAL call.

One thing that might not be obvious in the second script is that the “Instance” value is formatted like this: “”https://login.microsoftonline.com/{0}” so we’re using the “-f” string format function to pop the tenant ID into that {0} placeholder. (I took that functionality from an online sample I found somewhere, but I may change that around, since I think it just confuses things.) Also, in the first example, I added “/.default” to the $scope variable in the script, while the new version already has that in the config file.

I’m not sure if any of this will ever be useful to anyone but me, but it seems like something that might help someone else out there on the internet somewhere, at some point.