どうしてもWindows10の起動サウンドを鳴らしたい

期限が迫っているのに、どーでもいいことが気になって時間食ってしまう💦
どーでもいいプログラムを書く時間です。

歴代のWindowsで一番気に入ってるスタートアップサウンドは、Windows2000とMe の時のピアノ音。あれが一番心地よい。まぁ人それぞれなんですが・・・

最近のWindowsって起動サウンドが鳴らないんですね・・・ググると Windows8ぐらいからデフォルトで無効にされているみたいですね。まぁ仕事場のパソコンは別にいいとして、家で使うPCぐらい鳴らしてもいいんじゃないすかねぇ。

で、そもそも コンパネのサウンドの設定で、起動時のサウンドを鳴らす設定が消されています。なんで?
これもググるとレジストリをいじることで有効になるみたいですが、結局はシステムにブロックされて起動サウンドは鳴らないみたいでつね。。。

しかたないので、タスクスケジューラに登録することにした。起動サウンドのWAVファイルは Windows2000のインストールCDから抜いた。ライセンス的に不味いかもしれん。が、別にばら撒くわけでもなし。

基本タスクを作成し、トリガーとして「ログイン時」を選択。次に登録するプログラムなんですが・・・Windows標準で、サウンドファイルを再生するコマンドはないみたいです・・・
これもググると Powershell のワンライナーがヒットします。。。というか、これしかヒットしません。
要点は・・・コマンドプロンプトで下記コマンドを打つだけ。

>> powershell -WindowStyle hidden -c (New-Object Media.SoundPlayer "wavファイルのパス").PlaySync();

このコマンドをそのままタスクスケジューラに登録してもいいんですが・・・コンソールウィンドウが立ち上がってしまいます。

なんか、すっげぇカッコ悪りぃ!

標準でできないならプログラムを組むしかない。 上記のコマンドラインは .NET Frameworkのクラスライブラリ System.Media.SoundPlayer のインスタンスを生成してサウンドファイルを渡すだけなので、C#でも数行で済みそうです。というわけで組んでみる・・・組むというレベルでもない。

/*******************************************
  * JUST ONLY PLAY SOUND FILE(WAV)

  File name : playsound.cs
  build: 
     >> csc -target:winexe  playsound.cs
  
  usage:
     >> playsound logon.wav

  namespaceは適当に変えてね。
*******************************************/
using System.IO;
using System.Media;

namespace jp.ptsv.windows
{
  class PlaySound
  {
    static void Main(string [] args)
    {
      if(args.Length > 0 && File.Exists(args[0]))
        (new SoundPlayer(args[0])).PlaySync();
    }
  }
}

主要部分は一行ですね(笑)
これを、-target:winexe 付きでビルドするとコンソールウィンドウなしのプログラムができるので、このプログラムを適当のフォルダに配置してサウンドファイルとともにタスクスケジューラに登録します。
c:\your\binary\path\playsound.exe logon.wav

やっとこさ、ログイン時に あの Windows2000 の心地よい起動サウンドが聞けるようになったとさ。おわり。

一応実行バイナリを置いておきますが、自己責任で。いかなる損害も補償は一切いたしません。あしからず。

LAMP環境からSQLServerインスタンスの接続ポート番号を取得する

制作中の業務用ウェブシステムがSQLServer縛りなので、ここ2~3年急にSQLServerをいじることが多くなってきています💦 別の大半の仕事は MySQLかSQLiteなのですが、やはり WindowsのデファクトスタンダードのDBMSはSQLServer、ツール類は断トツに整備されていじりやすい!

そんなことはともかく。いつも前置きが長いです。すみません。

通常SQLServerインスタンスへコンピューター名(or IPアドレス)\インスタンス名 で接続するので特に接続ポート番号(動的ポート)を意識せずとも接続できると思われます。

ただ、僕はLAMP環境メインでやってきているので、ASP.NETとかいうのは肌に合いませんし💦 今更関わろうとも思いません。(C#でのデスクトップアプリやツール類の作成は趣味でやっているのですが・・・)
Linux側からWindows Server+SQLServerにする場合、大抵の場合SQLServerインスタンスのポートを固定して、ファイヤーウォールでそのポート番号を開けて・・・というやり方が一番ラクですし、今開発中のものもそういう環境でやると思います。

で、ふと思ったのですが、動的ポートって「動的」って言うんだから変わることを前提にしないといけないなぁ・・・と漠然と思いまして、カンタンなSQLServerインスタンスの情報を取得する方法がないかと思いまして・・・このエントリはその時に調べた内容です。

Windowsクライアントメイン(大概がAccessランタイムアプリ)の職場環境だと、大抵の場合「名前付きインスタンス」で動的ポートになっていると思います。職場環境によってはポート固定への変更を頑なに拒まれることもあろうかと思いますし、結局のところ一体何番で接続していいのか困ることがあります。

とりあえず、ググるが、SQLServer Browserサービスに直接問合せることで、SQLServerインスタンスの情報を得られますよ~、的な、事しかわかりません。

マイクロソフトのドキュメントによると、SQLServer Browserへの問合せはSSRP(SQL Server Resolution Protocol)というプロトコルでアクセスするみたいです。
[MC-SQLR]: SQL Server Resolution Protocol

これを読むと、UDP/1434ポート(SQLServer Browserサービス)へ、下図のように、インスタンス名の前に0x04をつけて問い合わせると、セミコロン区切りで情報を投げてくれるそうです。

(上記URLのドキュメントより抜粋)

なんだ!カンタンじゃん!(ほんとかよ。。。)

もちろんですが、SQLServerホストのファイヤーウォールでUDP/1434ポートを開けておく必要がありますが、普通は空いているでしょう、さもないと外部クライアントから一切接続できなくなるし。

とりあえず、ターミナルからローカルで動いているSQLServer(Deverlopper edition)ホストへ netcat で叩いてみる。
インスタンス名は既定のインスタンス(MSSQLSERVER)。

$ echo -ne "\04mssqlserver" | nc -u localhost 1434
Y ServerName;XXXXXX;InstanceName;MSSQLSERVER;IsClustered;No;Version;14.0.1000.169;tcp;1433;;

一応ローカルで動かしているSQLServerは固定ポートで動かしているので 1433になってまつね。。。
これだけだとプロトコル・ポート番号とインスタンスのSQLServerのバージョンなどが取れます。IsClusteredは・・・なんなんでしょうね?無知ですみません。

結果のフォーマットは、先頭(0バイト名)に1バイトの識別記号?の\x05、1バイト目にペイロード長(終端ヌルを除く)を示す16ビット整数、3バイト目からペイロード・・・という風な感じ。ペイロードはセミコロン区切りで、名前1;値1;名前2;値2;….;; という風なフォーマット。これなら簡単に連想配列かなんかにパースできます。

んで、インスタンスの簡易情報を取得するコードを書いてみる。

<?php
/*************************************************************************************
SQLServer名前付きインスタンスの情報を得る。
File: query_sqlbrowser.php
*************************************************************************************/
function queryNamedInstance($host,$instanceName,$queryPort = 1434)
{
  static $timeout = 5;
  $fp = fsockopen('udp://'.$host,$queryPort,$errno,$errstr,$timeout);
  if($fp === false)
    throw new RuntimeException($errstr);

  $str = sprintf('%s%s%s',hex2bin('04'),$instanceName,hex2bin('00'));

  if(false === fwrite($fp,$str,strlen($str)))
    throw new RuntimeException(_('failed to fwrite to resouce pointer'));

  $rv = fread($fp,1500);
  fclose($fp);

  $head = substr($rv,0,1);
  list(,$len) = unpack('v',substr($rv,1,2));
  $data = explode(';',substr($rv,3,$len));
  $rv = array();
  $i = 0;
  while(array_key_exists($i,$data) && array_key_exists($i+1,$data))
  {
    if(!empty($data[$i]))
      $rv[$data[$i]] = $data[$i+1];

    $i+=2;
  }

  return $rv;
}

この関数を実行してみると

<?php
require 'query_sqlbrowser.php';
print_r(queryNamedInstance('IPアドレス','インスタンス名'));

/***** 出力 *****
Array
(
  [ServerName] => コンピュータ名
  [InstanceName] => インスタンス名
  [IsClustered] => No
  [Version] => 10.50.4000.0
  [tcp] => 49179
)
****************/

とりあえす、これで得られたポート番号をキャッシュしておき、接続試行がタイムアウトしたら、キャッシュ更新・・・という感じで組み込もうかと思います。

samba(linux)とgVim(windows)でハマる

おっさんは、忘れてしまうのが異常に早いので、忘れずに備忘録。
ドキュメント書いたことも忘れてしまう。意味ないじゃ~~~ん。

閑話休題。

開発マシンを新しくCentOS7にした時、ファイル共有(Samba4)の設定で罠にはまった。
気づいたのは、CentOS7上に共有ディレクトリを設定して、Windows10の gVim(Kaoriya)でdockerの Dockerfile と docker-compose.yaml 他諸々の自動化シェルスクリプトを編集してた時に気づきました。

「あれ? (Windows10の) gVimで編集して保存(:w)したとき、groupのパーミッションに実行ビットが勝手につきやがるな・・・」

↓の図で Dockerfile のパーミッションに見慣れない(+)記号と group のパーミッションに x が付きます。。。

/etc/samba/smb.conf の設定で、create_maskとかforce create mode とかいろいろ試行錯誤するも状況に変化なし。不思議なことに メモ帳で開いて、保存すると、上記のようなことは起こらない。
Windows版のvim/gVimの時だけそうなる。。。

で、見慣れない + 記号の意味を調べると、Windowsの拡張属性?みたいなものがくっつくと + 記号が出るみたい。。。
そこで、sambaのリファレンスからそれらしい設定を見つけた。

  • map archive
  • map system
  • map hidden
  • nt acl support

DOSでおなじみ、A,H,S属性をLinux側とマッピング?するオプションなのかな?
nt acl support はそのものずばり、WindowsのACLのサポートをするかどうか。
とりあえず、これらを片っ端から no に設定して samba を再起動したら直った・・・・と思ったけど、今度は実行ビットが立ったファイルをgVim(windows)で編集して保存すると、実行ビットがなくなる。。。
smb.confで、create mask を 644 にしているのでは???と思い、gVim の保存方法に問題があるんじゃねーの?って推定して、ググると、どうやら backupcopy 変数が関係しているらしい、とのこと。

:helpで調べると unix以外のvimでは backupcopyの初期値は auto だそうで、backup=yes を ~/_vimrc に追加すると、僕の期待した通りの挙動に一応なりました。

ってなわけで、Linux側で共有されたファイルをWindowsのgVimで編集するときは、:set backupcopy=yes で、/etc/samba/smb.conf の map **** と nt acl support を no に設定。

ブラウザのヘッドレスモードでスクショ

ホームページのスクリーンショットを撮る作業を何とか自動化したい・・・ということで、ずっと前は phantomjs 一択だったような・・・ちょっと前に開発終了してて、今はGoogle Chrome、Mozilla FireFox自体が既にヘッドレスモードをサポートしている、との事。

Window版のChrome/FireFox 及びWSL(ubuntu 1604)上のLinux版での導入手順の備忘録です。

まずは、Windows(x64)版で試してみます。
Windows10を使用していますが、すでに Chrome/FireFoxともインストール済みです。
コマンドラインから手軽に使えるように環境変数にChrome/Firefoxのインストール先のディレクトリパスを登録しときます。
僕はあまり環境を汚したくないので 下記のようなコマンドファイル(chrome.cmd/firefox.cmd)を作って現在パスが通ってるディレクトリに放り込んでます(C:\Windows ディレクトリとか(^^;。

@echo off
"Chromeのexeファイルのフルパス" 

FireFoxも同様

さて、ヘッドレスモードは、–headless オプションつけて起動します。
このヘッドレスモードは node.jsやその他のスクリプト言語から、制御するのですが、スクショ撮りだけなら、chrome/firefoxとも -screenshotオプションが用意されていますので、簡単です。

# FireFoxの場合
>> firefox.cmd -headless -screenshot スクショ.jpg https://www.yahoo.co.jp/ --window-size=1024

# Chromeの場合
>> chrome.cmd --headless --disable-gpu --screenshot https://www.yahoo.co.jp/ --window-size=1024,768

微妙にオプションの付け方違いますので本家サイトで要調査です。
FireFoxの場合、カレントディレクトリに画像が作られます。ウィンドウサイズも横幅だけ指定しておけば、高さは自動的に決めてくれますし、ラクです。
chromeの場合は、ちょっとクセがあって、まず、ヘッドレスモードは管理者モードが必要みたいです。コマンドプロンプトを管理者モードで立ち上げないとおそらく失敗します。また、画像の名前は screenshot.png と固定みたい?で、保存されるディレクトリも chromeの実行ファイルと同じディレクトリで固定みたいです。ちょっと使い勝手が悪いです。後述する Linux(ubuntu)版ではカレントディレクトリに作られるのでこの辺は直して欲しいなぁ。。。

実際の業務では、撮ったスクショを ImageMagick でリサイズしたりして加工する、一連のスクリプトを組んで運用します。
注意点が一つ。間違ったURLを指定すると、制御が戻ってこないのでタスクマネージャーもしくは taskkillコマンドで殺すしかありません(^^;

次にLinux版(WSL)です。
具体的な導入手順です。ヘッドレスモードでの使用なので、Xサーバーは必要ありません。

一点、ご注意を。chrome/firefoxとも、日本語フォントが無い場合スクショに豆腐フォントになります。
WSLの場合は、/usr/share/fonts に Windowsのフォントディレクトリのシンボリックを作ればいいみたいですね。

$ sudo ln -s /mnt/c/Windows/Fonts /usr/share/fonts/windows
$ fc-cache -fv 

FireFox編
普通に sudo apt install firefox でインストールします。
使用するときのオプション指定は、上記 Windows版と同じです。

google-chrome編

$ wget -nd https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
$ dpkg -i ./google-chrome-stable_current_amd64.deb

※このパッケージをインストールすると googleのリポジトリが追加されます。追加されたくない場合は、

$ sudo touch /etc/default/google-chrome

4/6現在のバージョン 73.0.3683.xxx は問題(バグ?)があるらしく、ヘッドレスモードで立ち上げるとエラーで落ちます。そのため、以前のバージョンにダウングレードしないと使えません。(WSLの固有の問題なのかも?)

$ google-chrome --headless --no-sandbox --disable-gpu --screenshot https://www.yahoo.co.jp/ --window-size=1024,768
[0406/230503.514352:FATAL:gpu_data_manager_impl_private.cc(892)] The display compositor is frequently crashing. Goodbye.
Failed to generate minidump.Illegal instruction (コアダンプ)

古いバージョンは、ググれば見つけられると思います。僕は71.0.3578.80をダウンロードし、上書きインストールしました。

また、WSLで chromeを使用する場合、WSL自体を管理者モードで立ち上げないと使えないみたいです。普通にWSLを使用すると、–no-sandboxが必要です。–no-sandboxをつけるとセキュリティ低下を伴うので、特にこだわりがない場合は FireFoxを常用するのがいいのかも。

#普通にWSLを立ち上げた場合、--no-sandoboxを付けないと以下のエラーがでます。
$ google-chrome --headless  --disable-gpu --screenshot https://www.yahoo.co.jp/ --window-size=1024,768
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Permission denied
Failed to generate minidump.Illegal instruction (コアダンプ)

#管理者モードで立ち上げると
$ google-chrome --headless --disable-gpu --screenshot https://www.yahoo.co.jp/ --window-size=1024,768
[0406/230947.581201:ERROR:gpu_process_transport_factory.cc(967)] Lost UI shared context.
[0406/230948.477496:INFO:headless_shell.cc(546)] Written to file screenshot.png.

gvim(+kaoriya) + WSL Part3

:prev の続き

※この内容は事前に Part1で記述したように shell,shellcmdflagとかの変数をWSL用に変更する必要があります。

とりあえず、gVim(+kaoriya) と WSLの連携をある程度スムーズにできるようになりましたが、問題が一つ発生。
ローカルドライブだと問題はないのですが、リムーバルディスクとかネットワークドライブだと、WSL内の /etc/fstab に記述して自動マウントしてても、Windows10アプリケーションからWSLに移行した時点でカレントディレクトリが引き継げません。

ネットワークドライブを ドライブレター(Z: とか)にアサインして、cmd.exeで cd /d z: としてカレントディレクトリを変更し、bash.exe(or wsl.exe)しても、ホームディレクトリに飛ばされます。。。
カレントディレクトリを引き継いでくれるのは、c:ドライブとか、ローカルハーディスクだけのようです。ディスクタイプによって判断しているんでしょうか・・・。

これが一番困るのは、:grep コマンド。:grep コマンドの結果をQuickFixで開けるようにしているけど、カレントディレクトリ以下でローカルドライブだとなんとか使えるけど、リムーバルドライブとか、ネットワークドライブだと、パスの関係で全滅。WSLのフルパスで検索対象を渡せば grep は機能するが、QuickFixウィンドウに表示されるのはWSL側のフルパスなので 当然 gVim ではそのパスが理解できないので、結局使えない。

解決方法は、3つ。

(1) :grep をあきらめ、:vim(grep) で代替。
  ⇒ (ネットワークドライブだと特に)遅い。常用できないほど遅い。

(2) Msys の grep.exe で代替
 ( _vimrc にて set grepprg=/mnt/c/Msys64/usr/bin/grep.exe\ -n

  ⇒ WSLのみでなんとかする、という最初の趣旨から外れてしまうが・・・しょうがない。

(3) WSL側に grepの結果からパス名部分を変換するフィルタスクリプトをかます
 ( _vimrc にて set grepprg=grepwsl\ -n

  ⇒ ネットワークドライブ内のファイルを編集している時だけ検索パスをフルパス(WSL)を指定しないといけない。
  ⇒ また、若干遅くなるが、許容範囲内。。。WSLだけで完結できる。

というわけでとりあえず しばらく(3)を常用することにした。すでに Msys入れている場合は(2)の方が簡単かも・・・。ネットワークドライブのときだけ検索パスを絶対パスにする必要があるが、まぁしょうがない。

まずネットワークドライブ(例: Y)をドライブレターにアサインし、 sudo mkdir /mnt/yでディレクトリを作成し、/etc/fstabに登録する。

# /etc/fstab
LABEL=cloudimg-rootfs   /        ext4   defaults        0 0
Y: /mnt/y drvfs defaults,noatime,uid=1000,gid=1000 0 0

WSL側のpathが通っているところに(たとえば /usr/local/bin/ とか)grepwsl を作成しsudo chmod +x /usr/local/bin/grepwsl

#!/bin/sh
##############################################################################
#  wslgrep  .... WSLに入っているwslpathコマンドを使用してパスを変換 
#  2018/08/29 絶対パスに変換するオプション(-a)を追加/正規表現ちょい修正
##############################################################################
grep $@ | perl -pe 's/^([^:]+)/`wslpath -m -a $1 | tr -d "\n"`/e;'

これは単にgrepの結果を perl に渡して変換処理をしてるだけ。sedでもawkでもなんでも。単にperlに慣れているだけ。

んで、~/_vimrc に追記

;;; ~/_vimrc
set grepprg=grepwsl\ -n

ってなけで、とりあえず、なんとかなった!(^^;