Turn on/off Bluetooth radio/adapter from cmd/powershell in Windows 10

This is challenging because of the necessary interoperation with WinRT, but it is possible in pure PowerShell:

[CmdletBinding()] Param (
    [Parameter(Mandatory=$true)][ValidateSet('Off', 'On')][string]$BluetoothStatus
)
If ((Get-Service bthserv).Status -eq 'Stopped') { Start-Service bthserv }
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
}
[Windows.Devices.Radios.Radio,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
[Windows.Devices.Radios.RadioAccessStatus,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
$radios = Await ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]])
$bluetooth = $radios | ? { $_.Kind -eq 'Bluetooth' }
[Windows.Devices.Radios.RadioState,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
Await ($bluetooth.SetStateAsync($BluetoothStatus)) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null

To use it, save it is a PS1 file, e.g. bluetooth.ps1. If you haven't already, follow the instructions in the Enabling Scripts section of the PowerShell tag wiki to enable the execution of scripts on your system. Then you can run it from a PowerShell prompt like this:

.\bluetooth.ps1 -BluetoothStatus On

To turn Bluetooth off, pass Off instead.

To run it from a batch file:

powershell -command .\bluetooth.ps1 -BluetoothStatus On

Caveat: If the Bluetooth Support Service is not running, the script attempts to start it because otherwise, WinRT will not see Bluetooth radios. Alas, the service cannot be started if the script is not running as administrator. To make that unnecessary, you can change the startup type of that service to automatic.

Now for some explanation. The first three lines establish the parameters the script takes. Before beginning in earnest, we make sure the Bluetooth Support Service is running and start it if not. We then load the System.Runtime.WindowsRuntime assembly so that we can use the WindowsRuntimeSystemExtensions.AsTask method to convert WinRT-style tasks (which .NET/PowerShell doesn't understand) to .NET Tasks. That particular method has a boatload of different parameter sets which seem to trip up PowerShell's overload resolution, so in the next line we get the specific one that takes only a resultful WinRT task. Then we define a function that we'll use several times to extract a result of the appropriate type from an asynchronous WinRT task. Following that function's declaration, we load two necessary types from WinRT metadata. The remainder of the script is pretty much just a PowerShell translation of the C# code you wrote in your answer; it uses the Radio WinRT class to find and configure the Bluetooth radio.


After giving up to seek for ready-made solutions, I've found out that the Universal Windows Platform apps have access to the radio control API.

So I merged a sample radio control app with a commandline-controlled app, and the result is here: https://github.com/peci1/RadioControl . It's published on Windows Store.

In general, it isn't possible to just let the app run in background, so you need to first launch the GUI, and then you can call it from the commandline like

radiocontrol.exe 0 on
radiocontrol.exe Bluetooth off

The core features that this app needs are:

using Windows.Devices.Radios;
await Radio.RequestAccessAsync();
var radios = await Radio.GetRadiosAsync();
await radios[0].SetStateAsync(RadioState.On);

Then you also need to add the Radios capability to the app manifest:

<DeviceCapability Name="radios" />

And to allow control from commandline, you need to override method protected override async void OnActivated(IActivatedEventArgs args) in App.xaml.cs and do the processing in a branch like this:

switch (args.Kind)
        {
            case ActivationKind.CommandLineLaunch:

This Autohotkey script seems to do the trick. I am bothered by the Sleep there but it seems the window becomes active before it is ready to receive my keystrokes.

SendMode Input
Run, ms-settings:bluetooth
WinWaitActive, Settings
Sleep 300
Send,{Tab}{Space}
WinClose, A

Crossposted to https://stackoverflow.com/q/48700268/308851 to figure out what that Sleep 300 could be replaced with.