VSCodeの拡張機能が動かなくなった

Visual Studio Code拡張機能が動かなくなった。 原因としては markdown-all-in-one という拡張機能がうまくアンインストールできていなかったから。 調査の記録を記事として書きました。

f:id:itib:20220121145438p:plain

現象

拡張機能を使おうとすると Command 'Live Share: Join Collaboration Session' resulted in an error (command 'liveshare.join not found) と表示されて拡張機能が動作しない。 LiveShareだけかと思ったらその他の拡張機能も同様に開くことができなくなっていた。

調査

拡張機能が存在するか調査

code --list-extentions コマンドでVSCodeに入っている拡張機能一覧を取得する。

ref: Managing Extensions in Visual Studio Code

❯ code --list-extensions
4ops.terraform
aws-scripting-guy.cform
christian-kohler.path-intellisense
CoenraadS.bracket-pair-colorizer
Darfka.vbscript
DavidAnson.vscode-markdownlint
donjayamanne.githistory
eamodio.gitlens
esbenp.prettier-vscode
funkyremi.vscode-google-translate
GitHub.vscode-pull-request-github
Gruntfuggly.todo-tree
hashicorp.terraform
hediet.vscode-drawio
jebbs.plantuml
ms-azuretools.vscode-docker
ms-python.python
ms-python.vscode-pylance
ms-toolsai.jupyter
ms-toolsai.jupyter-keymap
ms-toolsai.jupyter-renderers
ms-vscode-remote.remote-containers
ms-vscode-remote.remote-ssh
ms-vscode-remote.remote-ssh-edit
ms-vscode-remote.remote-wsl
ms-vscode-remote.vscode-remote-extensionpack
ms-vscode.powershell
ms-vsliveshare.vsliveshare
ms-vsliveshare.vsliveshare-audio
ms-vsliveshare.vsliveshare-pack
redhat.java
redhat.vscode-commons
redhat.vscode-yaml
streetsidesoftware.code-spell-checker
Tyriar.sort-lines
VisualStudioExptTeam.vscodeintellicode
vscjava.vscode-java-debug
vscjava.vscode-java-dependency
vscjava.vscode-java-pack
vscjava.vscode-java-test
vscjava.vscode-maven

開けなかった拡張機能は存在していることになっている...

拡張機能ディレクトリにもしっかりあるか確認

❯ ls ~/.vscode/extensions
4ops.terraform-0.2.2
aws-scripting-guy.cform-0.0.24
christian-kohler.path-intellisense-2.6.0
christian-kohler.path-intellisense-2.6.1
coenraads.bracket-pair-colorizer-1.0.62
darfka.vbscript-1.0.4
davidanson.vscode-markdownlint-0.45.0
donjayamanne.githistory-0.6.19
eamodio.gitlens-11.7.0
esbenp.prettier-vscode-9.1.0
funkyremi.vscode-google-translate-1.4.13
github.vscode-pull-request-github-0.34.3
gruntfuggly.todo-tree-0.0.214
gruntfuggly.todo-tree-0.0.215
hashicorp.terraform-2.18.0
hashicorp.terraform-2.19.0
hediet.vscode-drawio-1.6.4
jebbs.plantuml-2.17.0
jebbs.plantuml-2.17.1
jebbs.plantuml-2.17.2
ms-azuretools.vscode-docker-1.18.0
ms-azuretools.vscode-docker-1.19.0
ms-python.python-2021.12.1559732655
ms-python.vscode-pylance-2021.11.0
ms-python.vscode-pylance-2022.1.0
ms-python.vscode-pylance-2022.1.1
ms-toolsai.jupyter-2021.10.1001414422
ms-toolsai.jupyter-2021.11.1001550889
ms-toolsai.jupyter-2021.9.1101343141
ms-toolsai.jupyter-keymap-1.0.0
ms-toolsai.jupyter-renderers-1.0.4
ms-vscode-remote.remote-containers-0.202.5
ms-vscode-remote.remote-containers-0.209.6
ms-vscode-remote.remote-ssh-0.70.0
ms-vscode-remote.remote-ssh-edit-0.70.0
ms-vscode-remote.remote-wsl-0.63.13
ms-vscode-remote.vscode-remote-extensionpack-0.21.0
ms-vscode.powershell-2021.12.0
ms-vsliveshare.vsliveshare-1.0.5065
ms-vsliveshare.vsliveshare-1.0.5242
ms-vsliveshare.vsliveshare-1.0.5273
ms-vsliveshare.vsliveshare-audio-0.1.91
ms-vsliveshare.vsliveshare-pack-0.4.0
redhat.java-1.2.0
redhat.vscode-commons-0.0.6
redhat.vscode-yaml-1.2.2
redhat.vscode-yaml-1.3.0
streetsidesoftware.code-spell-checker-2.0.14
streetsidesoftware.code-spell-checker-2.1.3
streetsidesoftware.code-spell-checker-2.1.4
tyriar.sort-lines-1.9.1
visualstudioexptteam.vscodeintellicode-1.2.15
vscjava.vscode-java-debug-0.37.0
vscjava.vscode-java-dependency-0.18.9
vscjava.vscode-java-pack-0.20.0
vscjava.vscode-java-test-0.33.1
vscjava.vscode-maven-0.34.2
yzhang.markdown-all-in-one-3.4.0

一覧表示してもわかりにくいので表示のdiffを取ります。

❯ diff -i <(ls ~/.vscode/extensions | sed 's/-[0-9].*$//g' | uniq) <(code --list-extensions)
42d41
< yzhang.markdown-all-in-one

なんかフォルダだけに残っている拡張機能がありますね👀

コマンドの解説

diffコマンド

対象のファイルの差分を出力します。 比較するファイルとしてコマンドの実行結果を渡すには <() を用いて名前付きパイプとするとあたかもファイルのように比較することができます。

まだ diff -i で大文字と小文字の変化を無視することができます。

qiita.com

lsとsedコマンド

みんなの強い味方 sed

-[数字] を空白に置換することで vscode/extentions の表示に含まれるバージョン情報を削除しています。

ls ~/.vscode/extensions | sed 's/-[0-9].*$//g'

~ ❯ ls ~/.vscode/extensions | tail -n 3
vscjava.vscode-maven-0.34.2
yzhang.markdown-all-in-one-3.4.0
~ ❯ ls ~/.vscode/extensions | sed 's/-[0-9].*$//g' | tail -n 3
vscjava.vscode-java-test
vscjava.vscode-maven
yzhang.markdown-all-in-one

uniq コマンド

表示行で重複するものを消せる。 複数バージョンの拡張機能があると複数フォルダが表示されて嶋う。uniq コマンドで重複を消している

検証

ファイルはあるけどVSCode側にインストールされていない拡張機能を削除。

❯ rm -rf ~/.vscode/extensions/yzhang.markdown-all-in-one-3.4.0

うまく動くようになった! f:id:itib:20220121153845p:plain

どうやら Markdown all in one を過去にアンインストールした際にファイルが残り続けてしまったことが原因らしい。

探してみるとネット上にもちらほらそんな記事が存在した。

qiita.com

最後に

無事に拡張機能が使えるようになってよかったです。 最近読んだシェルノック本がとても役立ちました。

いままでsedでどうやるんだっけ...とか悩むことがありましたがその頻度も減ってきていい感じです。 おすすめ。

Windowsイベントログ解析ツール「Hayabusa」を使ってみる

こんにちは、いちび( @itiB_S144 )です。

2021年12月25日にWindowsイベントログ解析ツールとして「Hayabusa」がリリースされました🎉

Hayabusaは事前に作成したルールに則ってWindowsイベントログを調査し、インシデントや何かしらのイベントが発生していないか高速に検知することができるツールです。 私も開発を微力ながらお手伝いさせていただきました。 本記事では多くの方にHayabusaを触っていただきたいため、簡単な使い方を紹介していきます。

github.com

詳細なHayabusaの実装についてはコミッターのKazuminさんの記事が参考になります、 ぜひご参照ください!

kazuminkun.hatenablog.com

Hayabusaとは

HayabusaはWindowsイベントログの解析ツールです。 事前に作成したルールに則って解析を行い、ルールに引っかかるものがあれば検知します。

Hayabusaは、日本のYamato Securityグループによって作られたWindowsイベントログのファストフォレンジックタイムライン生成およびスレットハンティングツールです。

hayabusa/README-Japanese.md at main · Yamato-Security/hayabusa · GitHub

ルールは独自に作成することも可能ですが、1000以上のルールがプリセットとして用意されており、手軽に試すことができます。 また日本のコミュニティが作っているため日本語のREADMEが充実しています。英語が苦手な方でも安心

ルールの例

Hayabusaリポジトリの中には独自に作られたルールと多くの研究者が作成したルールである Sigma をベースに変換したルールが配置されています。 上にあげた例だけでなく、マルウェア検知など様々なルールが用意されています。 自分でルールを作るのが難しいという方でも十二分にHayabusaを利用することができます。

Hayabusaを使ってみる

以下に用意されたサンプルWindowsイベントログファイルに対してHayabusaを実行してみます。 ルールファイルはHayabusaで用意されたものを用います。 github.com

環境

手順

1. GitHubから必要なファイルを入手

Hayabusaの実行ファイルとサンプルWindowsイベントログファイルを取得します。

Windows環境なので hayabusa-1.0.0-windows-x64-evtx-0.7.2.zip をダウンロードしました。evtx以降の 0.6.70.7.2 の違いは中で使用しているライブラリのバージョンの違いです。とりあえず新しいほうを選んでいます。

f:id:itib:20211231172724p:plain

ファイルを解凍して準備OKです。

2. フォルダの確認

基本的に変更が発生するのは config フォルダになります。 大量発生してログを埋め尽くすようなルールをミュートしたり等々の設定ができます。

ひとまず今回はデフォルトのままで進めます。

├── hayabusa-1.0.0-windows-x64-evtx-0.7.2
│   ├── README.md
│   ├── hayabusa.exe
│   ├── config                // 設定ファイル
│   ├── doc                   // ドキュメントたち
│   ├── logs                  // hayabusa.exeの実行ログ
│   ├── rules                 // 検知ルールファイルたち
│   ├── sample-results
│   └── tools
└── hayabusa-sample-evtx-main // サンプルイベントファイルたち
    ├── DeepBlueCLI
    ├── EVTX-ATTACK-SAMPLES
    ├── EVTX-to-MITRE-Attack
    ├── README.md
    └── YamatoSecurity

3. Hayabusaの実行

コマンドプロンプトからHayabusaを起動します。 エクスプローラーでHayabusaフォルダに移動し、バーに cmd と入力することで簡単に開くことができます。

f:id:itib:20211231183542p:plain

オプションをなにもつけずに hayabusa.exe を実行するとヘルプが出てきます。

f:id:itib:20211231184229p:plain

ファイルに対してHayabusaを実行

hayabusa-sample-evtx に用意されたサンプルのWindowsイベントログファイル(.evtx)に対してHayabusaを実行します。

>hayabusa.exe -f <検査対象WIndowsイベントログ.evtx>

f:id:itib:20211231193000p:plain

検知した内容が表示されたのち、サマリが表示されます。 デフォルトのHayabusaが使用しているルールは /rules フォルダ下に配置されたすべてのルールです。 オプションから使用するルールを指定することも可能です。

フォルダに対してHayabusaを実行

windowsイベントファイルを置いたフォルダに対してまとめてHayabusaを実行することができます。

>hayabusa.exe -d ..\hayabusa-sample-evtx-main

その他

基本的な使い方はREADMEにすべて書かれています。 検出結果のファイルへの出力方法等は以下に記載されていますので参考にしてください。

hayabusa/README-Japanese.md at main · Yamato-Security/hayabusa · GitHub

自分のWindowsに対してHayabusa実行

サンプルのWindowsイベントログファイルにHayabusaを実行した後は自分のWindowsに対して実行してみたくなりますね? 早速試していきましょう。

新規Windowsユーザの作成

今回はログに残る動作として新たなユーザをWindowsに追加します。

設定 → アカウント → 他のユーザ からユーザを追加します。 f:id:itib:20211231193851p:plain

ログの取得

Windowsのイベントログファイルは C:\Windows\System32\winevt\Logs 下に保存されます。 管理者権限が必要となるため Hayabusa を管理者権限のあるコマンドプロンプトで開くかログファイルを管理者権限のいらないフォルダ下にコピーしましょう。

ユーザ追加のログは Security.evtx に保存されます。

f:id:itib:20211231194230p:plain

Hayabusaで調査する

コピーした Security.evtx に対してHayabusaをかけてみました。 User: test が作られたことを検知できていることがわかります。

f:id:itib:20211231195943p:plain

オプション -o をつけるとCSVファイルに検知結果を出力することができます。

>hayabusa.exe -f ..\Security.evtx -o detect.csv

detect.csv

Timestamp,Computer,EventID,Level,RuleTitle,Details,RulePath,FilePath
2021-12-31 19:38:37.370 +09:00,DESKTOP-ITIB,4728,low,User added to local security group,User: itib  :  Group: なし  :  LogonID: 0x4a111111,rules\hayabusa\default\alerts\Security\4728-AccountManipulation_UserAddedToLocalSecurityGroup.yml,..\Security.evtx
2021-12-31 19:38:37.370 +09:00,DESKTOP-ITIB,4728,medium,User added to global security group,User: itib  :  Group: なし  :  LogonID: 0x4a111111,rules\hayabusa\default\alerts\Security\4728_AccountManipulation_UserAddedToGlobalSecurityGroup.yml,..\Security.evtx
2021-12-31 19:38:37.372 +09:00,DESKTOP-ITIB,4720,medium,Local user account created,User: test  :  SID: S-1-1-11-111111111-1111111111-1111111111-1111,rules\hayabusa\default\alerts\Security\4720_CreateAccount-LocalAccount_UserAccountCreated.yml,..\Security.evtx

csvには検知したルールファイルへのパスが表示されています。

ルールファイルを見てみる

Hayabusaで調査するで検知したイベントログとルールファイルを見てみましょう。

Security.evtxをイベントビューアで開き、検索を駆使して検知したイベントを見つけました。 イベントログの EventID4720ChannelSecurity、作られたTargetUserNametestであることがわかります。後々のためにXML形式で見ています。

f:id:itib:20211231204223p:plain

上記イベントを検知した先ほどルールファイルは 4720_CreateAccount-LocalAccount_UserAccountCreated.yml でした。

author: Eric Conrad, Yamato Security
creation_date: 2020/11/08
uodated_date:  2021/12/22

title: Local user account created
title_jp: ローカルユーザアカウントが作成された
details: 'User: %TargetUserName%  :  SID: %TargetSid%'
details_jp: 'ユーザ名: %TargetUserName%  :  SID: %TargetSid%'
description: A local user account was created.
description_jp: ローカルユーザアカウントが作成された.

id: 13edce80-2b02-4469-8de4-a3e37271dcdb
level: medium
status: stable
detection:
    selection:
        Channel: Security
        EventID: 4720
    filter:
        TargetUserName|endswith: "$" #Filter out machine/computer accounts
    condition: selection and not filter
falsepositives:
    - system administrator
tags:
    - attack.persistence
    - attack.11136.001
references:
    - https://attack.mitre.org/techniques/T1136/001/
sample-evtx: ./sample-evtx/DeepBlueCLI/new-user-security.evtx
logsource: default
ruletype: Hayabusa

detection下に書かれた条件で検知が行われます。 ChannelSecurity AND EventID4720 であり、filter にマッチしない条件がこのルールで検知されるようです。

検知した場合は title: Local user account createddetails: 'User: %TargetUserName% : SID: %TargetSid%'%~% が埋められた文字列が検知結果として表示されています。

詳細なルールの書き方はこれまたREADMEに詳しく書かれていますので参考にしてください。

hayabusa/AboutRuleCreation-Japanese.md at main · Yamato-Security/hayabusa · GitHub

最後に

ここまで読んでいただきありがとうございました。

簡単なHayabusaの使い方を紹介させていただきました。 Hayabusa、面白そう!触ってみたいと思っていただけたら幸いです。

HayabusaはOSSとして大和セキュリティが開発しています。 Hayabusaの開発気になる、バグ見つけた等あればGithubのissueや下記ConnpassのSlackからコンタクトお願いします。

yamatosecurity.connpass.com

それではよいWindowsログ解析ライフを!

Windowsの音声出力先を変えるショートカット作成

本記事は ZOZO Advent Calendar 2021 の10日目の記事です。

みなさん在宅ワークしていますか? 自分はフルリモートの会社で働いているためどれだけ在宅環境をよくできるか日々考えています。

本記事では在宅ワークの際に手間だと感じていた 「パソコンの音声出力先変更」をショートカット1つでできるようにした方法を解説します。

どのように調べ、実現までもっていったか、参考になれば幸いです。

やりたいこと

パソコンで音楽を流したり好きなラジオ、配信を聞きながらテンションを上げて作業している方は多いと思います。 お昼時など席を離れた際にわざわざスマホに切り替えて、とかするのは大変ですよね?

我が家では普段の作業時には机の上のスピーカ、キッチンに移動したらおいてあるAlexaからBluetooth接続で音楽を再生させていました。 スピーカボタンを押してデバイスの選択という流れで音声の出力先を切り替えています。 f:id:itib:20211209005317g:plain

この作業がなかなかに手間。 移動開始前に数ステップある作業は嬉しくない。
理想は1クリックで移動可能、 なんなら移動したことを検知して勝手にスピーカを切り替えてほしい。

ということで本記事では「1クリックで音声出力先を切り替えられるようなショートカットを作成する」を目標に調査、実装を行っていきます。

調査

Windowsがどのようにデフォルトの音声出力先を決めているのか調査します。 どうやって設定しているかがわかれば対応したコードを書いて実行できるはず!

Windows APIの調査

Windowsの多くの動作はAPIを用いて操作することができます。 「Windows API インデックス」を漁っていきます。 音声出力先の設定に関わりそうなドキュメントとしては「Windows Core Audio API について」がありました。

コア オーディオ API は次のとおりです。
・マルチメディアデバイス(MMDevice)API。クライアントは、このAPIを使用して、システム内のオーディオエンドポイントデバイスを列挙します。
・...

列挙してくれるだけらしい🤔
フォーラムとかを見るとデバイスの切り替えのAPIは提供されていないように見えます。

ref: Programatically setting the default playback device (and recording device)

どこかのレジストリに格納されているのでは?

WIndowsの設定情報はレジストリに格納される。 当然音声出力先の情報もあるはず。

レジストリエディタで探すと \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\ 下に接続されたデバイスらしきデータが見つかりました。

f:id:itib:20211209010601p:plain

音声出力先を変更した際にどこの値が変化するか観察してみると出力先に設定したタイミングでUUID({xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}形式の値)下に含まれる Level: 0 ~ Level: 2 の値が変化するようです。

  • 値は出力に設定されるたびに+nされる
  • 別の値が出力先になっても値は変化しない
  • 足し算される値は期間を開けると大きくなる

ここから時間等でLevelに代入される値が決まっており、 値が最新のもの が音声出力先として使われていると考えることができます。

f:id:itib:20211209012429p:plain

レジストリの値は手動で編集することができるため右クリックで修正してみました。 再生先になっていないデバイスの値を現在の音声出力先となっているデバイスの値より大きくすれば出力先が変更されると予測していました。 とりあえず Level: 2 の値を +1 してみました。

変更前 変更後
Level: 0 0x000003cc 0x000003cc
Level: 1 0x000003cc 0x000003cc
Level: 2 0x0000020d 0x0000020e

値を変更した直後からWindows の右下サウンドマークで表示される出力先には値を大きくしたデバイスが表示されるようになったものの 音声の出力先はいままでの出力先のままという状態になりました。

f:id:itib:20211209020101p:plain

デスクトップの表示だけされても意味がないんだ...

既存実装調査

ここまでで自力では手詰まりだったのでGithubで先人たちの書いた実装を調べます。

Audioやいままでの調査で出てきたMMDevices等のキーワードたちでGithubを調べます。検索してみるとやりたいことを実装しているコードが何個も出てきます。以下は実装の際に大きく参考にしたリポジトリです。

先人は偉大。

github.com

github.com

気合で読んで以下のような流れで音声出力先を変更していることがわかりました。

  1. IPolicyConfig インタフェースを実装したPolicyConfigClientを作成
  2. PolicyConfigClientIPolicyConfig に含まれる SetDefaultDevice() を実装
  3. SetDefaultDevice()にデバイスのUUIDとシステムがオーディオエンドポイントデバイスに割り当てた役割を示すERoleを渡して呼び出す
  4. 出力先が変更される

実装する

先人たちのツールを使えば簡単ですが自分でもいろいろいじりたいと思い手を動かしていきます。実装する内容は以下の2つです。

  1. 出力先として利用可能なデバイス一覧を表示
  2. 音声出力先の切り替え

先人たちのコードが C# で書かれていましたが自分は C# の知見があまりないのできっとPowerShellならWindows 関連なんとかなるでしょ!という安直な考えでPowerShellで実装しました。

出力先として利用可能なデバイス一覧を表示

オーディオ関連のレジストリを調査した際に機器情報が格納されている場所がわかりました。指定した箇所のレジストリ内容を出力することでデバイス名を取得します。

$regRoot = "HKLM:\Software\Microsoft\"

function Get-Devices
{
    $regKey = $regRoot + "\Windows\CurrentVersion\MMDevices\Audio\Render\"
    Write-Output "Active Sound devices:"
    Get-ChildItem $regKey | Where-Object { $_.GetValue("DeviceState") -eq 1} |
        Foreach-Object {
            $subKey = $_.OpenSubKey("Properties")
            Write-Output ("  " + $subKey.GetValue("{a45c254e-df1c-4efd-8020-67d146a850e0},2"))
            Write-Output ("    " + $_.Name.Substring($_.Name.LastIndexOf("\")))
        }
}

Get-Devices

実行すると以下のようにデバイス名とUUIDが表示されます。

PS > .\deviceList.ps1
Active Sound devices:
  スピーカー
    \{476a4519-c338-4f9e-9746-294cd6429136}
  1-2
    \{4c884bad-d966-441a-b1f9-4ec0fced2fbc}
  3-4
    \{cd21a3a3-061e-4064-94a6-f5769eda2979}
  ヘッドホン
    \{ee3cf230-46d3-4043-8fc2-7cd4dff984b0}
  Digital Audio (S/PDIF)
    \{fa27ad22-6853-430a-b1e5-10b83f01dbbb}

音声出力先の切り替え

ここは先人たちのC#で書かれたコードをもとに実装しました。

あまりC#の知見がない + デバイス一覧取得をpowershellで書いたためできればpowershellで完結させたい、 と思っていたところpowershellは変数として与えたC#のコードを実行できるらしい。宇宙感じた。

social.technet.microsoft.com

必要となるERole, IPolicyConfig, PolicyConfigClientを実装しPowershellから呼び出すようにします。

$cSharpSourceCode = @"
using System;
using System.Runtime.InteropServices;

using System.Collections.Generic;
internal enum ERole : uint
{
    eConsole         = 0,
    eMultimedia      = 1,
    eCommunications  = 2,
    ERole_enum_count = 3
}
[Guid("F8679F50-850A-41CF-9C72-430F290290C8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IPolicyConfig
{
    [PreserveSig]
    int GetMixFormat();
    [PreserveSig]
    int GetDeviceFormat();
    [PreserveSig]
    int ResetDeviceFormat();
    [PreserveSig]
    int SetDeviceFormat();
    [PreserveSig]
    int GetProcessingPeriod();
    [PreserveSig]
    int SetProcessingPeriod();
    [PreserveSig]
    int GetShareMode();
    [PreserveSig]
    int SetShareMode();
    [PreserveSig]
    int GetPropertyValue();
    [PreserveSig]
    int SetPropertyValue();
    [PreserveSig]
    int SetDefaultEndpoint(
        [In] [MarshalAs(UnmanagedType.LPWStr)] string deviceId, 
        [In] [MarshalAs(UnmanagedType.U4)] ERole role);
    [PreserveSig]
    int SetEndpointVisibility();
}
[ComImport, Guid("870AF99C-171D-4F9E-AF0D-E63DF40C2BC9")]
internal class _CPolicyConfigClient
{
}
public class PolicyConfigClient
{
    public static int SetDefaultDevice(string deviceID)
    {
        IPolicyConfig _policyConfigClient = (new _CPolicyConfigClient() as IPolicyConfig);
        try {
            Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eConsole));
            Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eMultimedia));
            Marshal.ThrowExceptionForHR(_policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eCommunications));
            return 0;
        } catch {
            return 1;
        }
    }
}
"@

add-type -TypeDefinition $cSharpSourceCode

function Set-DefaultAudioDevice
{
    Param
    (
        [parameter(Mandatory=$true)]
        [string[]]
        $deviceId
    )

    If ([PolicyConfigClient]::SetDefaultDevice("{0.0.0.00000000}.$deviceId") -eq 0)
    {
        Write-Host "SUCCESS: The default audio device has been set."
    }
    Else
    {
        Write-Host "ERROR: There has been a problem setting the default audio device."
    }
}

$id = $args[0]
Set-DefaultAudioDevice $id

1つ目の引数として音声出力先に設定したいデバイスのUUIDを渡すとSetDefaultDeviceが実行され音声出力先を変更することができました。

PS > .\deviceSet.ps1 "{4c884bad-d966-441a-b1f9-4ec0fced2fbc}"
SUCCESS: The default audio device has been set.

スクリプト実行用のショートカット作成

作成したPowerShellコードを呼び出すショートカットを作成します。 Windowsのショートカットには引数を与えて作ることができるので出力先に対応したショートカットをデスクトップに作成します。

ショートカットを作成し、そのプロパティからリンク先を powershell.exe <作成したdeviceSet.ps1へのパス> <設定対象のUUID> に編集します。

サンプル: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe C:\Users\guest\deviceSet.ps1 "{4c884bad-d966-441a-b1f9-4ec0fced2fbc}"

f:id:itib:20211209214848p:plain

出来上がったショートカットをデスクトップに置くことで以下のように音声出力先を1クリックで変更できるようになりました。 自分の環境ではアイコン画像も変えてわかりやすくしました。

f:id:itib:20211209212542g:plain

おわりに

目標だった「1クリックでWindowsの音声出力先を変更する」を達成することができました。

手軽に再生先を変えられるようになっただけでなく、 Windowsの音声出力設定方法やシステムについて詳しくなれた、 PowerShellC#のコードを読みだせることを知れた点で挑戦してみてよかったと思います。

つぎはPowerShellから別の言語に書き換えたり、人の部屋移動を検知してこのショートカットを叩かせたりしたいです。

二分木(Binary Search Tree)の実装 その1

概要

シンプルな二分木をGolangで実装していきます。 二分木の動作は以下のURLがビジュアル化されていてわかりやすいです。

Binary Search Tree Visualization

まずは二分木のベースとして値の挿入をして木を作成し、 見つけたい値が木に含まれているかを判定する関数を作成します。

実装

木とノードの定義

type Node struct {
    Value int  // ノードの持つ値
    Left  *Node // 左側ノードへのポインタ
    Right *Node // 右側ノードへのポインタ
}

// 二分木として持つのはルートノードのみ
// ノードにアクセスするのはすべてルートからたどる
type Tree struct {
    Root *Node
}

代入処理

// TreeにNodeを加える
func (t *Tree) Insert(insertValue int) {
    if t.Root == nil {
        // 初回のNodeだった場合はTreeのRootとしてNodeを設定する
        // t.root = CreateNode(insertValue)
        t.Root = &Node{Value: insertValue}
    } else {
        parentNode := t.Root
        for {
            if insertValue >= parentNode.Value {
                // 右のノードを確認
                if parentNode.Right != nil {
                    // ノードが存在するならば移動
                    parentNode = parentNode.Right
                } else {
                    // ノードがまだ無ければノードを配置する
                    parentNode.Right = &Node{Value: insertValue}
                    break
                }
            } else {
                // 左のノードを確認して同様に代入OR移動
                if parentNode.Left != nil {
                    parentNode = parentNode.Left
                } else {
                    parentNode.Left = &Node{Value: insertValue}
                    break
                }
            }
        }
    }
}

テストの作成

値を代入して正しい位置にノードがあるかを判定するテストコードを作成し、正しく処理ができているか確認します。

ここでは 4, 2, 7, 6 の順に代入して目的の木ができているかを確認します。

f:id:itib:20211017145547p:plain
4, 2, 7, 6の順で代入した木

func TestCreateTree(t *testing.T) {
    var tree binary_search_tree.Tree
    tree.Insert(4)
    tree.Insert(2)
    tree.Insert(7)
    tree.Insert(6)

    if tree.Root.Value != 4 {
        t.Error("Failed")
    }
    if tree.Root.Left.Value != 2 {
        t.Error("Failed")
    }
    if tree.Root.Right.Value != 7 {
        t.Error("Failed")
    }
    if tree.Root.Right.Left.Value != 6 {
        t.Error("Failed")
    }
    if tree.Root.Right.Right != nil {
        t.Error("Failed")
    }
}

ディレクトリ構成

project
├ binary_search_tree
│ ├ binary_search_tree.go
│ └ binary_search_tree_test.go
└ go.mod

binary_search_tree.go

package binary_search_tree

type Node struct {
    Value int
    Left  *Node
    Right *Node
}

// NodeたちにアクセスするのはすべてRootから
// BinarySearchTreeとして知っているのはRootの場所のみ
type Tree struct {
    Root *Node
}

// TreeにNodeを加える
func (t *Tree) Insert(insertValue int) {
    if t.Root == nil {
        // 初回のNodeだった場合はTreeのRootとしてNodeを設定する
        // t.root = CreateNode(insertValue)
        t.Root = &Node{Value: insertValue}
    } else {
        parentNode := t.Root
        for {
            if insertValue >= parentNode.Value {
                // 右のノードを確認
                if parentNode.Right != nil {
                    // ノードが存在するならば移動
                    parentNode = parentNode.Right
                } else {
                    // ノードがまだ無ければノードを配置する
                    parentNode.Right = &Node{Value: insertValue}
                    break
                }
            } else {
                // 左のノードを確認して同様に代入OR移動
                if parentNode.Left != nil {
                    parentNode = parentNode.Left
                } else {
                    parentNode.Left = &Node{Value: insertValue}
                    break
                }
            }
        }
    }
}

binary_search_tree_test.go

package binary_search_tree_test

import (
    "testing"

    "github.com/itiB/project/binary_search_tree"
)

func TestCreateTree(t *testing.T) {
    var tree binary_search_tree.Tree
    tree.Insert(4)
    tree.Insert(2)
    tree.Insert(7)
    tree.Insert(6)

    if tree.Root.Value != 4 {
        t.Error("Failed")
    }
    if tree.Root.Left.Value != 2 {
        t.Error("Failed")
    }
    if tree.Root.Right.Value != 7 {
        t.Error("Failed")
    }
    if tree.Root.Right.Left.Value != 6 {
        t.Error("Failed")
    }
    if tree.Root.Right.Right != nil {
        t.Error("Failed")
    }
}

go.mod

module github.com/itiB/project

go 1.16

テスト結果正常に木が作成できていることがわかりました。

❯ go test ./binary_search_tree
ok      github.com/itiB/project/binary_search_tree   0.004s

検索したい値が木に含まれているか判定

検索対象の値が木に含まれているか調べるには代入する場所を探すのと同様に木を探索していって一致する値が含まれているかを調べることで実装できます。

func (tree *Tree) Contains(findValue int) bool {
    // そもそも木がなければ含まれていない
    if tree.Root == nil {
        return false
    }

    cursor := tree.Root // 調べているノードを指す
    for {
        // ノードの値が検索対象の値なら発見
        if cursor.Value == findValue {
            return true
        } else {
            // ノードを移動する処理
            // もし進むべき方向にノードがないならば値は含まれていないとしてfalse
            if findValue >= cursor.Value {
                if cursor.Right != nil {
                    cursor = cursor.Right
                } else {
                    return false
                }
            } else {
                if cursor.Left != nil {
                    cursor = cursor.Left
                } else {
                    return false
                }
            }
        }
    }
}

テストの作成

木を作成し、代入した値が含まれているかのテストを記述します。

func TestFindTree(t *testing.T) {
    var tree binary_search_tree.Tree
    tree.Insert(4)
    tree.Insert(2)
    tree.Insert(7)
    tree.Insert(6)
    tree.Insert(-5)

    if !tree.Contains(4) {
        t.Errorf("Failed")
    }
    if !tree.Contains(6) {
        t.Errorf("Failed")
    }
    if !tree.Contains(-5) {
        t.Errorf("Failed")
    }
    if tree.Contains(5) {
        t.Errorf("Failed")
    }
    if tree.Contains(500000000000) {
        t.Errorf("Failed")
    }
}
❯ go test ./binary_search_tree
ok      github.com/itiB/project/binary_search_tree   0.004s

まとめ

二分木のベースとなる処理を作成することができました。 次は削除処理を作成します。

Base64デコーダーを作る

本記事は以前筆者のqiitaに投稿していた記事を移動させたものです。

Base64デコーダーを作る - Qiita

とあるCrackmeでcustom_base64なるものでエンコードされた文字列が出てきた...
Flagを手に入れるためにはこれをデコードして元の文字列を見つけなきゃいけない. custom_base64とはなんなのか...答えを見つけるために,我々はアマゾンの奥地へと...

概要

Base64の仕組みの中で000000 ~ 111111を文字に置き換えた辞書が存在する. 通常のBase64では順番に ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ をあてはめた辞書が使われている. これを指定した辞書に入れ替えたものがcustom_base64関数の中身であった. Base64を実装する記事を参考に本記事ではPythonデコーダーを実装する.

Base64の仕組み

エンコード

大まかにBase64エンコードの処理を確認します.詳しくは他サイトを参照.

  1. 変更したい文字列(ASCII)をバイナリ(2進数)に変換
  2. バイナリを6bitづつに分割
  3. 分割した際に最後が6bitより少なくなるため,6bitになるように0を追加する
  4. 変換表を用いて6bitを文字に変換
  5. 4biteずつBase64では出力するために文字数が4の倍数文字になるよう"="を付け足す
  6. base64の文字列の完成!!

デコード

エンコードの仕組みがわかればデコードは簡単!基本的には逆の手順を踏むだけ!

  1. 付け足された"="を削除
    • SG9nZUhvZ2U=SG9nZUhvZ2U
  2. 変換表を用いて文字をバイナリに変換してつなげる.
    • SG9nZUhvZ2U010010 000110 111101 100111 011001 010100 100001 101111 011001 110110 010100
  3. バイナリを8bitずつに分割,エンコード3. で付け足された0が余るのでそれらを削除
    • 01001000 01101111 01100111 01100101 01001000 01101111 01100111 01100101 00
  4. 2進数bitをASCIIに変換
01001000 → H
01101111 → o
01100111 → g
01100101 → e
01001000 → H
01101111 → o
01100111 → g
01100101 → e
  1. デコード完了!

プログラムにしてみる

import sys
import argparse
BYTE_SIZE = 8

# 000000 -> 111111まで1文字ずつ辞書型リストを作成する関数
def makeDict(base64Dict_seed):
    dictionary = {}

    for i in range(0, 64):
        dictionary[format(i, '06b')] = base64Dict_seed[i]

    return dictionary


# 文字列s をn文字で区切ってリスト化する関数
def split(string, n):
    split_list = []

    for i in range(0, len(string), n):
        split_list.append(string[i:i+n])

    return split_list


# 文字列がn文字なかったらn文字になるように`c`を足す
def fillBlank(s, n, c):
    mod = len(s) % n

    if mod == 0:
        return s
    else:
        margin = n - mod
        return s + c * margin


# 辞書の値を渡すと辞書のキーを返す
def getValue(key, items):
    for v in items.items():
        # print(v[1])
        if v[1] == key:
            # print(v)
            return v[0]
    return ''


def main():
    # -kをつけるとカスタム辞書を入力できる
    parser = argparse.ArgumentParser(
    description='custom Base64 Decoder')
    parser.add_argument('-k', '--key', help="Use custom Seed to encrypt in base64 ", \
        default="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
    parser.add_argument('text', help='base text')
    args = parser.parse_args()


    # 0. 辞書を作る
    base64Dict = makeDict(args.key)


    # 1. '='をはずす
    text = args.text.replace("=", '')

    binStr = ""


    # 2. 変換表を用いて文字をバイナリに変換してつなげる.
    for i in text:
        binStr += getValue(i, base64Dict)

    # 3. バイナリを8bitずつに分割,エンコード3. で付け足された0が余るのでそれらを削除
    splitCount = 8
    s = split(binStr, splitCount)

    if (len(s[-1]) != 8):
        s.pop(-1)


    # 4. 2進数bitをASCIIに変換
    result =""

    for c in s:
        print(c + " → " + chr(int(c, 2)))
        result += chr(int(c, 2))

    print(result)

if __name__ == "__main__":
    main()

使い方

$ python3 customBase64Decoder.py <Base64テキスト>
$ python3 customBase64Decoder.py -k <Custom辞書> <Base64テキスト>
$ python3 customBase64Decoder.py SG9nZUhvZ2U=
$ python3 customBase64Decoder.py -k xEPOKnvADqeG0m1VkZ47CM653jrtbzLsTc2ypoYUSWJ9ludQig+awf8XF/RNHBhI 4vBUjCcQj8C=

HogeHoge

まとめ

Base64完全に理解した. これでオリジナルBase64つくって秘密の通信ができちゃうね,やったね

サンプルコードはGitHubに置いてあります

参考文献

base64ってなんぞ??理解のために実装してみた - qiita https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d41

M5StickCを動かしてみる

本記事は筆者が以前Qiitaに投稿していた記事を移動させたものです。

とりあえず動かせるとこまでやりました.

今回はArduino IDEで動かしてみます. Image from Gyazo

環境

  • Windows10
  • M5StickCがある

環境つくるよ

必要なものをインストール

  1. Arduino IDE
    https://www.arduino.cc/en/Main/Software
  2. USB to Serial Driver
    https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/drivers/CP210x_VCP_Windows.zip

Arduino IDEはサイトに飛んで"Download the Arduino IDE"より,対応するものをダウンロードしてインストールしてください.
USB to Serial Driverはリンクをクリックするとzipがダウンロードされます.
解凍すると中にいくつかのファイルがあると思います.対応するexeを選んで実行してください.

バージョン インストーラ
Windows10 32ビット版 CP210xVCPInstaller_x86_v6.7.0.0.exe
Windows10 64ビット版 CP210xVCPInstaller_x64_v6.7.0.0.exe
Windows7 CP210xVCPInstaller_Win7_v5.40.24.exe

うまくUSB to Serial Driverが入ったか実際にM5StickCをつないで確認していきます.

  1. . M5StickCをUSBで接続,
  2. . Windowsのデバイスマネージャを起動
  3. . ポートの中にUSB Serial Portが現れるはずです.(COM*)はUSBを刺したポート等で変わると思います. Image from Gyazo

Arduino IDEの設定

Arduino IDEを開いて実際にM5StickCを動かせるように準備していきます.

  1. . "ファイル -> 環境設定" を開く
  2. . "追加ボードマネージャのURL" に
    https://dl.espressif.com/dl/package_esp32_index.json を入力してOK
  3. . "ツール -> ボード"Ard...." -> ボードマネージャ" を開く
  4. . 検索バーで"esp32"を検索
  5. . でてきたのをインストールしたのち閉じる Image from Gyazo
  6. . "スケッチ -> ライブラリをインクルード -> ライブラリを管理" を開く
  7. . 検索バーでm5stickを検索,インストールしたのち閉じる Image from Gyazo
  8. . "ツール -> ボード -> M5Stick-C" を選ぶ
  9. . ツールのなかにボード情報とかが表示されるようになった Image from Gyazo

テストプログラムを動かしてみる!

"ファイル -> スケッチ例 -> M5StickC" のなかに様々なテストプログラムがあるので好きなものを選んでください.

ファイルとかの下にある右向きの矢印を押すとプログラムがコンパイルされ転送されます!

まとめ

かなりはやくここまで来れました. テストプログラムをたくさん用意してくれているのでそれで遊んでるだけでも結構たのしいです. こっからいろいろプログラム頑張って書いていきます. また気が向いたら記事にします.

言いたいこと等あれば以下まで Twitter: https://twitter.com/itiB_S144

参考記事

いろいろとM5StickCについて検証してくださっているわかりやすい記事様↓↓ Lang-ship: https://lang-ship.com/blog/

qiita.com