Monday, February 26, 2018

SprintDLL does not currently do arrays

I happened to notice recently that it's not possible to work with arbitrarily sized arrays of things in SprintDLL. You can allocate arbitrarily sized buffers, but the slot kind has to be a pointer. Though it is possible to avoid invalid cast errors by using the offset keyword of copyslot, the copied value goes after the pointer to the buffer, not actually in the buffer, so that either mangles the buffer pointer or writes to unallocated memory. For fixed size arrays, you can use block slots and the field keyword, but that gets unwieldy for large arrays and doesn't work at all for arbitrarily sized arrays. I will see about correcting this oversight.

Sunday, February 25, 2018

The -File and -Directory switches on Get-ChildItem didn't always exist

The Get-ChildItem cmdlet has -File and -Directory switches that limit the results to files and subdirectories, respectively. These are very nice, but I found when writing scripts for people on older versions of Windows (specifically Windows 7 or earlier) that they didn't always exist. To support those versions, you have to use the PSIsContainer property. So this is equivalent to gci -File:

gci | ? { -not $_.PSIsContainer }

For directories, of course, the PSIsContainer property is true.

Saturday, February 24, 2018

Unmangling ASCII text interpreted as Unicode

Sometimes programs take buffers of ASCII strings (or 8-bit characters in general) and mistakenly pass them to functions that expect Unicode (16-bit) strings. This causes pairs of 8-bit characters to fuse together, combining their byte representations into a Unicode code point. To attempt* to recover the original ASCII from nonsense Unicode on the clipboard, you can use this PowerShell command:
[System.Text.Encoding]::ASCII.GetString([System.Text.Encoding]::Unicode.GetBytes((gcb)))

*The attempt might not be 100% successful if the smashed-together bytes form invalid UTF-16 sequences. The very last character of the string can be lost if the original text had a non-multiple-of-two length.

Based on my Super User answer.

Friday, February 23, 2018

If a program's UI is tiny or jumbled, override the DPI

My laptop's high-DPI 4K screen reveals a lot of programs' DPI-related misbehavior. Some programs try to implement DPI awareness but actually draw some UI controls really tiny (relative to the huge resolution) or get the controls jumbled up and overlapping. The solution is to override their declaration of DPI awareness and have the system do the fuzzy but proportional scaling. That can be done in the Compatibility tab of the Properties window of the shortcut or executable - checking the "override high DPI scaling behavior" box and choosing System in the dropdown list does the job. In versions of Windows prior to 10, there's just one DPI-related checkbox and no dropdown; checking the box should be sufficient.

Wednesday, February 21, 2018

PowerShell remembers commands from closed windows

Occasionally I close PowerShell windows before remembering to look at some details in the commands I used (especially when there are a lot of numbers involved). New versions of PowerShell store the console history across sessions, so pressing the up arrow in a new prompt will show the last command executed in any PowerShell window. You can even press Ctrl+R to search for a command you used.

Sunday, February 18, 2018

PowerShell nested functions won't affect variables in the outer scope

I recently converted a handful of PowerShell scripts to a single script file that defines several functions. I noticed that the script that used a helper function to do its job no longer worked; the nested function's changes to the variables created by the parent function weren't preserved outside the nested function. Previously I had been using $script:variableName in the inner function instead of $variableName, but since they were no longer script-scoped, that didn't help.

Some Googling turned up this Stack Overflow Q&A that suggested using Set-Variable to create the variables with the AllScope option. That appears to have worked. I can now do this to initialize them:

Set-Variable -Name someText, someBool, anotherBool -Option AllScope
$someText = ''
$someBool = $false
$anotherBool = $false

They can then be used in both the inner and outer functions without any special qualifiers.

Disabling PowerShell's "can't backspace any more" beep

Sometimes if you attempt to backspace beyond the beginning of the line, PowerShell will emit a beep. If you would prefer that PowerShell be quiet, you can use this command to change that option:

Set-PSReadlineOption -BellStyle None

Saturday, February 17, 2018

GetRadiosAsync won't list Bluetooth radios if bthserv isn't running

The GetRadiosAsync WinRT method is documented to return a collection of the communication radios on the system. I discovered on Stack Overflow while researching for this answer that it will not find any Bluetooth radios if the Bluetooth Support Service (bthserv) is not running. If the service is running, the function will successfully find all radios, even those that are disabled. That service has a trigger to start under some circumstance that I can't determine exactly (an RPC event of some kind), but my script has to rely on starting it through the normal channels.

Wednesday, February 14, 2018

SprintDLL - More little improvements

Today I made a few more little changes to SprintDLL to fix and improve some things:

  • If the actual P/Invoke method throws a TargetInvocationException when called, error reports now show the inner exception's message rather than the generic "exception was thrown by the target of an invocation" one. Previously, trying to use a nonexistent function or a function in a nonexistent DLL just produced the generic message, which was not very enlightening.
  • Using the /into switch of the call instruction to return a value into an existing slot now actually works. Previously it always showed the "return type doesn't match type of slot" error message, oops.
  • There is now a zeroslot instruction that, unsurprisingly, sets a slot to zero/null.
  • The newslot instruction no longer creates an uninitialized slot when an error is encountered while parsing the initial value. Previously the uninitialized slot had to be removed with deleteslot before trying the creation again.

Monday, February 12, 2018

Policy Plus - Need more forgiving ADMX loader

A Policy Plus issue was filed today stating that the program refuses to proceed when there is a duplicated namespace. There aren't supposed to be duplicated namespaces in correctly written ADMX bundles, but apparently there is in the newest Windows Insider build. The Local Group Policy Editor can handle this even though it complains when you start it, so Policy Plus should be at least as helpful. I'll have to see how to be forgiving (or at least informative) without getting the ADMX workspace into an inconsistent state.

Sunday, February 11, 2018

Installing a PowerShell module for all instances

Downloaded PowerShell modules can be imported into the current session using the Import-Module cmdlet. To have them be available automatically, they must be placed in an appropriate folder. To affect all users on the machine, they go in:

C:\Program Files\WindowsPowerShell\Modules

To affect only the current user, they go in:

~\Documents\WindowsPowerShell\Modules

...where ~ is the user's home directory.

The module folder must be named exactly the same as the file title of the module manifest (the PSD1 file).

Saturday, February 10, 2018

FMod - v2.10

Today I published Abiathar v2.10. The changes included are the tour, the new Configuration dialog, and mini-palettes being persisted. It had been a while since any published changes - this is the first update of 2018, so I went into the assembly information and updated the copyright year to 2014-2018.

Friday, February 9, 2018

Using WinRT's IAsyncOperation in PowerShell

WinRT types can be used from PowerShell if explicitly named first. Many WinRT API methods are asynchronous, returning genericized IAsyncOperation objects that come into PowerShell as System.__ComObject. Trying to use any methods on such objects fails. Some people have written compiled assemblies in C# that convert async operations to standard .NET tasks and then await them, but this can be accomplished in pure PowerShell with some reflection:
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
Function Await($WinRtTask, $ResultType) {
 $asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
 $netTask = $asTask.Invoke($null, @($WinRtTask))
 $netTask.Wait(-1) | Out-Null
 $netTask.Result
}

The Await function takes the IAsyncObject returned by a WinRT function and the type of the result. It returns the task's result. Example:
Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus])

The parentheses around the arguments are important, otherwise PowerShell tries to be helpful and interpret them as string literals.

Originally developed for my Super User answer.

Wednesday, February 7, 2018

SprintDLL - Documenting with GitHub

Since I'm distributing SprintDLL using GitHub's release system, it seems appropriate to document it using GitHub's wiki system. The program has been in desperate need of documenting for a long time - there is no in-program help and the only usage guidance is what can be pieced together from scouring these posts. I created a very barebones intro page that at least lets users do something with the tool. EventuallyTM I'll flesh it out and add pages explaining each instruction.

Tuesday, February 6, 2018

DataContract fields can change to strings without problems

Recently I needed to expand a DataContract-serialized field to hold arbitrary strings, not just integers, optimally without breaking existing serialized objects. Looking at the XML shows that there isn't anything identifying a field as necessarily being any specific simple type. Changing the class definition of the serialized type to have a field be a string instead of something more structured works fine in terms of loading old data.

Sunday, February 4, 2018

Function calls passed to other functions might need parentheses

One user was trying to call a function and pass the resulting array of strings to another function, like so:

Func2 Func1

The idea was to call Func2 with the results of Func1 as the parameter. The problem here, though, is that PowerShell interprets Func1 as a string literal despite it not being quoted. (It's trying to be helpful.) The solution is to parenthesize the argument:

Func2 (Func1)

This actually calls Func1 and passes that to Func2.

You can add fields to a new version of a DataContract, but they might not be initialized

.NET DataContracts are a convenient way to serialize objects as XML. Sometimes it's necessary to add a field to a serialized data structure. This keeps compatibility with older serialized strings, but one thing to watch out for is that the new field may not be initialized at all when loading an old string. Even if the field is explicitly given a default value when defining the type, it comes out null when loading a serialized string that doesn't include that field, so there could be some null reference surprises.

Saturday, February 3, 2018

Mysterious parameter binding errors? Wrap casts in parentheses

Today I dealt with some mysterious PowerShell errors associated with Add-Member. This code...

$x = New-Object psobject
$x | Add-Member NoteProperty 'SomeProp' [int]($something.someString)

...produced this error...
Add-Member : The SecondValue parameter is not necessary for a member of type "NoteProperty", and should not be
specified. Do not specify the SecondValue parameter when you add members of this type.
At line:1 char:6
+ $x | Add-Member NoteProperty 'SomeProp' [int]($something.someString)
+      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Add-Member], InvalidOperationException
    + FullyQualifiedErrorId : Value2ShouldNotBeSpecified,Microsoft.PowerShell.Commands.AddMemberCommand

I had added the parentheses around the string value because otherwise SomeProp became some text including what I meant to be the cast. Adding explicit parameter names to the Add-Member call had no effect. The solution is to wrap the entire cast in parentheses:

$x | Add-Member NoteProperty 'SomeProp' ([int]($something.someString))

Thursday, February 1, 2018

FMod - Persistent mini-palettes

For quite a while now, Abiathar has had mini-palettes, user-decidable arrangements of 9 (10 if you count the ephemeral "previously selected tile" one) tiles for each plane. These are useful if you have a few tiles that are moderately tricky to find - common enough to want to have on hand but not easy to pick out of the tileset or current level. They weren't persisted across Abiathar runs, though, so you had to manually get your tiles arranged again if you restarted. That's pretty unfortunate, so today I made them be persisted in project files. As a side effect of that, they are no longer preserved across opens of different project files, which I think is a good thing because the tile IDs could mean something entirely different in different projects.