ロック画面のスポットライト画像をコピーしてデスクトップにも流用させたい

ロック画面は何にしてますか?僕は設定でWindowsスポットライトにしてます。
これ、たぶんBingから定期的にダウンロードしてロック画面にスライドショー的に表示していると思うんだけど、なぜかログイン後のデスクトップの背景の設定でWindowsスポットライトの選択肢がないんですよねぇ。

Microsoft Storeで壁紙系のアプリを探せばたぶんあると思いますが、たぶんに要らん機能がくっついてきたり個人情報が抜かれたりと面倒なのでやっぱり自分でなんとかするのが基本です。そうです、信じられるのは唯一自分なのです(笑)

Windowsスポットライトで使用される画像ファイルは、
%LOCALAPPDATA%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets
に保存されているようです。拡張子がないので判別はできませんが、概ね400KB以上のファイルサイズのファイルが該当する画像で、任意のフォルダにコピーして拡張子.JPGでリネームすれば使いまわせます。ライセンスとか分からんが、どこぞに配布するわけではないので大丈夫でしょう。

定期的にピクチャフォルダにコピーすればいいんですが・・・いちいちフォルダ開いてファイルを選別してコピーしてリネームして・・・うわぁぁぁあーーーーーーん、超絶メンドクセーーーーーーー。
さらに、バックグラウンドでダウンロードされる画像ファイルには縦長と横長がごちゃまぜになっている。縦長の画像は不要なので、これも選別項目です。さらにメンドクサイ。

UNIX系のOSなら find コマンドやらimagemagickを駆使してシェルスクリプト化すればいいんですが・・・。
PowerShellを使えばできそうですけど、んーーーー、C#で組んだ方が早ぇよ。。。ということで、組んでみる。

ファイルサイズを判別するのは簡単で、JPEGファイルの高さと幅を取得するのがメンドクサそうですが・・・単純にSOFマーカーから取得することで手抜きします。完璧にしようとすると僕の知識では無理です。

/******************************************************************
  FileName : CopyWindowsSpotLight.cs

  使用方法:
    CopyWindowsSpotLight.exe コピー元 コピー先
  
  コピー元は 上記の %LOCALAPPDATA%..... を指定する。
 
  コピー元のディレクトリとコピー先のディレクトリを指定して次の条件でコピーする。
 1)ファイルサイズ400KB以上のファイルをJPEGファイルとしてみなす。
 (本来はファイルの先頭32ビットぐらいを読んでJPEGファイルかの判別をする必要がありますが・・・)
 2)ランドスケープ(幅が高さより大きい)
 3)既にコピーしたしたファイルはスキップする。
 4)コピーするとき 拡張子 .jpg を付けてコピーする

 どのタイミングでスポットライトの画像がダウンロードされるのか分からないので、
 実際にはタスクスケジューラに登録して定期的に実行させます。

  コピー元はハードコードしてもいいかも・・・。
******************************************************************/
using System;
using System.Text;
using System.IO;
using System.Linq;

namespace org.ptsv.console
{
  class CopySpotlight
  {
    // エントリポイント
    static int Main(string [] args)
    {
      if(args.Length < 2)
      {
        Console.Error.WriteLine("Usage: command <source directory> <destination directory>");
        return -1;
      }

      char[] trimChars = { '\\' , ' ' };
      var src = args[0].TrimEnd(trimChars);
      var dest = args[1].TrimEnd(trimChars);

      if(!Directory.Exists(src) || !Directory.Exists(dest))
        Console.Error.WriteLine("ディレクトリが存在しません。");

      // コピー元のディレクトリのファイル一覧を取得してフィルタリングしていきます。
      // 最終的に プロパティ s,d を持つ匿名クラスに変換して foreach で回していきます。
      // 本来はtry-catchで囲むべきですが・・・
      var enumerator = Directory
        .EnumerateFiles(src)
        .Where(f => (new FileInfo(f)).Length >= 400 * 1024)
        .Where(IsLandscape)
        .Select(f => new { s = f,d = dest + "\\" + Path.GetFileName(f) + ".jpg"});

      foreach(var set in enumerator)
      {
        try {
          File.Copy(set.s,set.d);
          Console.Error.WriteLine("done copied 1 file.");
        } catch (Exception e) {
          Console.Error.WriteLine("skip copy since file already exists.");
        }
      }

      return 0;
    }

  // 値をスワップする(マイクロソフトのドキュメントからコピペ )
  // https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/generic-methods
    protected static void Swap<T>(ref T lhs, ref T rhs)
    {
      T temp;
      temp = lhs;
      lhs = rhs;
      rhs = temp;
    }

    // JPEGファイルの縦横サイズから 幅が高さより大きいものを選択できるようにする
    // BitConverterってリトルエンディアン(x86系なら殆どコレだと思うけど)しか対応してないみたいですねぇ。。。
    // SOFマーカーから取得できるサイズってビックエンディアンで記録されている?のでひと手間必要。ハマった。
    // Swapせずそのまま得られた16bit整数を System.Net.IPAddress.HostToNetworkOrder関数で変換するって方法もある。
    // どっちにしろメンドクセー。
    // 追記: 単純に 2バイトから16bitを合成する関数かけばよかった。
    // protected static int ConvertToUInt16(byte a,byte b)
    // {
    //    return (a << 8) | b;
    // }
    protected static bool IsLandscape(string f)
    {
      ushort width = 0,height = 0;
      using (FileStream fs = File.OpenRead(f))
      {
        int bufSize = 4096;
        byte[] content = new byte[bufSize];
        int readLen = 0;
        bool isReadBreak = false;

        // ファイル全てを読み込むのは効率悪いので細切れで読んでいきます。
        // 大抵は先頭に記述してあると思うので一回で済むと思います。
        // 追記:2020/05/04
        // これだとバッファ境界付近に SOFマーカーが現れたとき、index + 5,とかindex + 8とかのアクセスでエラーで落ちる。
        // 下記コードは不完全だと気付きました(遅)💦 やはり一旦すべてを読むか、
        // SOFマーカーが見つかったらその位置から再度fs.Readしなおすか、とかにする必要がある。時間あったらまた修正しよ。
        while(0 < (readLen = fs.Read(content,0,content.Length)))
        {
          for(int index = 0; index < content.Length; index++)
          {
            if (0xFF == content[index] && 0xC0 == content[index + 1])
            {
              // プラットフォームがリトルエンディアンの場合は、
              // 5バイト目と6バイト目、7バイト目と8バイト目をスワップする
              if(BitConverter.IsLittleEndian)
              {
                Swap<byte>(ref content[index + 5],ref content[index + 6]);
                Swap<byte>(ref content[index + 7],ref content[index + 8]);
              }
              height = BitConverter.ToUInt16(content,index + 5);
              width =  BitConverter.ToUInt16(content,index + 7);
              isReadBreak = true;
              break;
            }
          }
          if(isReadBreak)
            break;

          Array.Clear(content,0,readLen);
        }
        content = null;
      }
      return width > 0 && height > 0 && width >= height;
    }
  }
}

一応コンパイルした実行ファイルを置いときますが、自己責任でお願いします。いかなる理由での損害には応じられません。

JavaScript Map オレオレ拡張

今、かつてないほどJavaScriptと関わっている。
※JavaScriptという名称は正式にはOracleの登録商標で一般的には ECMA Script って事になるんだろうけど、めんどくさいので JavaScript と明記する。あらかじめご了承のほど。

僕のJavaScriptの文法、その他の知識は正直2010年(ES5)ぐらいで止まってる。というのも今持っている知識だけで要求されるほとんどのケースが実現可能だからだ。
もちろんパフォーマンス的な、効率的な・・・というのは棚に上げまくっているんだけど。その当時も ES6 も意識していましたが、まだブラウザでの実装状況が混沌としててInternetExplorerがまだまだ全盛時代だったので、どうしてもすべてのケースで動くように ES5 を強制していた。

今の JavaScript って5~6年前によく見たコーディングスタイルとは全然違いますよね。
ブラウザでホスティングされているJavaScriptで普通にラムダ式が使えるし、変数宣言で var ではなく let を使うことでブロックスコープを意識したコーディングができる。
まぁ、今頃こんなこと書いても、今更感が半端ないんですけどね。

去年から現在進行形で携わっている仕事では、徐々に(少しずつ) ES6 を意識したコーディングを心がけるようになってきました。ほんとに少しずつですけどね💦

余談:(余談ばっかりだが・・・)——————
理由はマイクロソフトが正式に Internet Explorer 及び edge-HTMLをベースにしたEdgeブラウザのサポートを止める、もしくは新規開発しない、事を宣言したから。
残念なことだけど、ITリテラシーの低い人たちからすれば、未だにInternet Explorer がインターネット業界(笑)の標準だと思っている人が結構多い。
マイクロソフトがレガシーブラウザと決別宣言してくれたおかげで、ChromeやFireFox,Chromium版Edgeを強制することが可能になったことが非常に大きい。
———————-

一番意識しているのは(オブジェクト初期化子{}でインスタンスを作る)オブジェクトを連想配列的に使用するのを止めてMap や Set を使うことにしたこと。
MapやSetもかなり以前から実装されているけど、やっぱり InternetExplorer11 では限定的な実装なので使うのも躊躇してた。

Map,Set は非常に便利ですよね。オブジェクトを使用した連想配列では、キー(プロパティー)に文字列ぐらいしか使えない。Mapだとキーに何でも入る。
MDNでの説明だと、頻繁に削除・挿入を繰り返すケースでパフォーマンスが上がるんだそうで。

たいがいの場合、キーには文字列を使うのが大半の用途だと思います。DOM要素やオブジェクトもキーにできると思うけど、正直僕には使いどころが分からない。まぁ、思いつくのは コールバック(関数)なんかをためておく用途にするぐらい。それでも、そういう時は Map よりSetを使うし。。。

でも特にブラウザ上でDOMとか扱っていると、やっぱりプレーンなオブジェクトの方がコーディングが楽な時もある。ので、Map.prototype空間にオレオレMap拡張メソッドを追加して使うことになってくる。

※2020/04/02 ちょっと追記した。

Map.prototype.toPlainObject

たぶん誰もが書いてる・・・と思う、文字通り、文字列をキーにしたMapをオブジェクトに変換する。
最新のブラウザとかだと、Object.fromEntries とかいう Object.entriesの逆動作を行う関数が実装されているので↓は不要なのかも。

Map.prototype.toPlainObject = function()
{
  var rv = {};
  this.forEach(function(v,k) { 
    if(typeof k === 'string')
      rv[k] = v;
  });
  return rv;
};
// もしくはもっと簡単に・・・
Map.prototype.toPlainObject = function()
{
  return Object.fromEntries(this);
};

Map.from

オブジェクトからMapへの変換は・・・↓でいいのかな? Object.entries は比較的新しめのブラウザしか対応してなくない?

Map.from = function(o)
{
  return new Map(Object.entries(o));
};

Map.prototype.stringify

JSON.stringify のもろパクリですね。toString をオーバーライドすればいいんでしょうけど、そこまでするのは止めましょう。
主に localStorage/SessionStorage へ格納するときに使う。

Map.prototype.stringify = function()
{
  return JSON.stringify(this.toPlainObject());
};

Map.parse

これも JSON.parse のパクリ。JSONのパクリというか、文字列を直接parseするのではなく、一旦オブジェクトにJSON.parseで変換して Map に変換します💦

Map.parse = function(str)
{
  return new Map(Object.entries(JSON.parse(str)));
};

Map.prototype.numIncr/Map.prototype.numDecr

オブジェクトだと、普通にobj.counter++ とか、obj.counter += 10 とかできますよね。。。。
Mapだと一旦 getで取得してsetで更新しないといけません。JavaScriptは演算子を定義もしくはオーバーロードできませんよねぇ。。。。
演算子のオーバーロードは混乱の元になるので極力やっちゃいけない、ってC++の時言われてたな・・・。今もそうなのかなぁ。。。

Map.prototype.numIncr = function(key,delta)
{
  if(typeof delta !== 'number' || delta == 0)
    delta = 1;

  var value = this.get(key);
  if(typeof value === 'number')
  {
    value += delta;
    this.set(key,value);
  } 
  return this;
};
Map.prototype.numDecr = function(key,delta)
{
  if(typeof delta !== 'number' || delta == 0)
    delta = 1;

  return this.numIncr(key,-1 * delta);
};

なんか、Mapの良さ(キーはなんでもOK)を殺してしまうようなものばかりだな。。。キーをstringに限定するようなものあればな~・・・とは思いますが、型が強制されない言語だと難しい。

ざっとよく使うものを列挙しました。自分がコピペできるように💦
ここまで書いてて、気づいたのは・・・var使いすぎ問題! ついつい癖で var って書いてしまうんす。
ラムダ式もイマイチ好きになれない記法だったりします。 C#だと当たり前のよう書くんですけどね。。。好き嫌いというより単に癖なのかも。

編集可能な簡易グリッドビュー画面をイチから作ってみる

直接関係はありませんが、これ の続きです。

ここにきて、カンタンなグリッドを表示する仕事があって、それならセルの内容を書き換えるだけの簡易的なセル編集機能もあればなぁ、と家に帰ってから唐突に思いつき、テレビを見ながら数時間ぐらいで突貫工事で作った。

仕様的には・・・
(1)Excelファイル(もしくはUTF-8なCSVファイル)をブラウザに読込み、
(2)簡単なグリッドビュー表示を行い、
(3)セルをクリックしたら編集し、
(4)リターンキー押下で編集確定、
(5)エスケープキーで編集取消
という感じ。

これならHTMLとCSS、JavaScriptで百数十行ぐらいでいけそうです。
実際業務で使うには、変更内容を追跡し、サーバーへ送信してデータベースに反映して・・・というような事が考えられますが・・・そこまでやっとれん!w

まずは、見てくれ、画面デザインですが、あくまでサンプル的なものなので、ほとんど素のHTMLです。
CSSのGridなんて初めて書いてみました。普段はBootstrapのレイアウトグリッドを利用して画面を作っているんですが・・・CSSのグリッドって、なんか難解ですね・・・ドキュメントを一回読んだだけではオッサンには理解できません(笑) エクセルの読込には、みんな大好き SheetJS ライブラリを利用しました。

ただ、セルを結合しまくったりしてるような複雑なエクセルファイルとか読み込むとたぶん滅茶苦茶になります。そういう複雑なエクセルファイルを持っていないので試していないんだけど・・・

デモ画面はこちら

CSS自体はそんなに凝ったことしてないので、読めば大体やろうとしていることが分かるでしょう。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style type="text/css">
      /* グリッドコンテナ */
      #excel-grid {
        height: 500px;
        overflow: auto;
        font-size: .8rem;
        position: relative;
      }

      /* グリッドの各行のスタイル */ 
      #excel-grid > .row {
        display: grid;
        column-gap: 0px;
        row-gap: 0px;
        border-top: 1px solid #999;
        border-left: 1px solid #999;
      }

      /* グリッドヘッダの固定(position: sticky) */
      .row.head {
        font-weight: bold;
        border-bottom: 2px solid #ccc !important;
        position: -webkit-sticky !important;
        position: sticky !important;
        z-index: 2 !important;
        top:0 !important;
        background-color: #544a65;
        color:white;
      }

      /* セルのスタイル */
      .row > .cell {
        height: 1rem;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        border-bottom: 1px solid #999;
        border-right: 1px solid #999;
        display: block;
        padding: 5px;
      }

      /* グリッドヘッダのセル部分 */
      .head > .cell {
        padding: 10px 5px !important;
      }

      /*セル編集時のフォーカス表示*/
      .row > .cell.editing {
        outline: 3px solid #bdceff;
        box-shadow: 0 0 10px 2px royalblue;
      }

    </style>
    <!-- jQuery,SheetJS ライブラリのロード -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.15.5/xlsx.full.min.js"></script>
  </head>

  <body>
    <h1 class="header">
      <span>超手抜きグリッドビュー</span>
      <input type="file" id="select-file">
    </h1>

    <!-- グリッドエリア -->
    <div id="excel-grid">
      <div class="row head"></div>
      <div class="row body"></div>
    </div>

    <!-- スクリプト本体は下記参照 -->
    <script type="text/javascript" src="./show-sheet.js"></script>
  </body>
</html>

続いて、肝心のJavaScript ですが、ほとんど各種イベントの処理ぐらいで、これといって特筆するべき事はありません。
ところどころ jQuery 使って見苦しいのですが・・・jQueryだと明らかにタイプ量が減るので僕は多用しています。世間の流れとしてはjQueryは排除されつつあるのですが・・・やっぱりjQueryに慣れきってしまうと document.querySelector なんてタイプするのがすんげぇ面倒。jQueryだと $ の一文字ですよ?イベント登録も on の二文字ですよ? addEventListener とかタイプするのがアホらしくなりますけどね、オッサンは。

/*******************************************************************************
 ミッション:
 ローカルのエクセルファイルを選択して疑似グリッドビュー編集を行う。
*******************************************************************************/
(function($,undefined) {

  // エクセルファイルロードのトリガー
  $('input#select-file[type=file]').on('change',on_input_type_file_change);
  
  // グリッドセルのイベントハンドラ登録
  $('.row.body')
    .on('click','.cell',on_cell_click)
    .on('blur','.cell',on_cell_blur)
    .on('keydown','.cell',on_cell_keydown);

  // ウィンドウサイズ変更のイベントハンドラ登録
  $(window).on('resize',on_window_resize).resize();


  /************************************************************************
   * 以下関数定義
  ************************************************************************/

  // グリッドの高さをウィンドウに合わせて調整
  function on_window_resize(e)
  {
    var h1 = $('.header').outerHeight(true);
    var h2 = $(window).height();
    var h3 = parseInt($(document.body).css('margin-top')) + parseInt($(document.body).css('margin-bottom')); 
    var h = h2 - h1 - h3;

    //画面下まで高さを伸ばす
    $('#excel-grid').height(h);
  }

  // エクセルファイルの読込完了ハンドラ
  function on_excel_file_load(e)
  {
    // お約束
    var data = new Uint8Array(e.target.result);
    var workbook = XLSX.read(data, {type: 'array'});

    // 一番最初のシートの名前を得る
    var n = workbook.SheetNames[0];

    // シート名からワークシートオブジェクトをゲト
    var ws = workbook.Sheets[n];

    // シートの全範囲を取得
    // ワークシートオブジェクトの各セルは、エクセルでよく使うA1形式でアクセスできます(ws['A1'], ws['A2']みたいに)
    // そして !ref プロパティーには A1:Z10のような形式でセルが使用されている範囲が入っています。
    // プログラムで利用するにはメンドクサイのでこの形式を一旦 row/column形式にデコードする必要があります。
    // ワークシートオブジェクトについては https://github.com/sheetjs/sheetjs/#sheet-objects を参照。
    var range = XLSX.utils.decode_range(ws['!ref']);

    // 使用されている最大列数を取得
    // デコードされた範囲オブジェクトrange は 
    // { s: { r: 数字, c: 数字 }, e: { r: 数字, c: 数字 } }
    // というような構造になっているようです。
    // s は start , e は end , r は row , c は column の略だと思われます。 
    var rownum = range.e.c + 1;

    // グリッド内のセルを初期化
    $('.row.head,.row.body').empty();

    // 最大列数から CSS のgrid-template-columnsプロパティの値を決めてあげます。
    // grid-template-columns: repeat(x,1fr); ここで x は rownumにする
    $('.row.head,.row.body').css('grid-template-columns','repeat(' + rownum.toString() + ',1fr)');

    // ワークシートをJSONデータに変換
    // 第二引数のオブジェクトで headerプロパティを与えていますが、
    // これは 各行をオブジェクト(object)ではなく配列(array)に変換するオプションです。
    // このオプションの意味は https://github.com/sheetjs/sheetjs#user-content-json を参照。
    var json = XLSX.utils.sheet_to_json(ws,{header:1})

    // <table>要素を使えばもっと簡単になったけど、敢えてイバラの道を進みます。
    var $head = $('#excel-grid > .head');
    var $body = $('#excel-grid > .body');

    // JSONデータから グリッドのセルを埋めていきます。
    $.each(json,function(i,arr) {
      var to = i == 0 ? $head : $body;
      for(var j=0;j<rownum;j++)
      {
        var v = arr[j] ? arr[j].toString() : '';
        var celldata = {'r':i,'c':j,'v':v};
        var attrs = {
          'data-cell': JSON.stringify(celldata),
          'title': v
        };
        $('<span>')
          .addClass('cell')
          .attr(attrs)
          .text(v)
          .appendTo(to);
      }
    });
  };

  // input[type=file]の onchange ハンドラ
  function on_input_type_file_change(e)
  {
    var files = this.files;
    var f = files[0];
    var reader = new FileReader();

    reader.onload = on_excel_file_load;
    reader.readAsArrayBuffer(f);
  }

  // 各セルをクリックした時のイベントハンドラ
  function on_cell_click(e)
  {
    e.preventDefault();
    e.stopPropagation();
    // セルをクリックしたらセルのclassにeditingを追加
    $(this).addClass('editing');

    // セルの元の値を undoプロパティに保存
    this.undo = this.textContent;

    // セルのスタイルを変更
    this.setAttribute('style','text-overflow: clip;');

    // セルを編集可能にする
    this.setAttribute('contentEditable','true');

    // フォーカスをあてる
    this.focus();
  }

  // セル編集中にフォーカスが外れたら cell_value_update関数をコールして更新
  function on_cell_blur(e)
  {
    if('true' === this.getAttribute('contentEditable'))
      cell_value_update(this);
  }

  // セルのonkeydownイベントハンドラ
  function on_cell_keydown(e)
  {
    // セル編集中、リターン(エンター)キーとエスケープキーをトラップ
    if(e.keyCode == 13 || e.keyCode == 27)
    {
      e.preventDefault();

      // エスケープキーなら復帰
      if(e.keyCode == 27)
        this.textContent = this.undo;

      cell_value_update(this);
    }
  }

  // cell 更新処理
  function cell_value_update(cell)
  {
    // セルの値が保存されていた編集前の値と違っていたら更新
    if(cell.textContent !== cell.undo)
    {
      var o = JSON.parse($(cell).attr('data-cell'));
      o.v = $(cell).text();
      $(cell).attr('data-cell',JSON.stringify(o));
    }

    // バックアップを廃棄
    delete cell.undo;

    // 編集を終了
    $(cell)
      .removeAttr('contentEditable')
      .removeAttr('style')
      .removeClass('editing')
      .scrollLeft(0);
  }

})(jQuery);

いつも利用させてもらってる疑似テストデータを利用してエクセルデータを流し込んでやると、こんな感じです。「こんな感じです」って言われてもね。。。

いちおう、エクセルファイルから簡易グリッドビューにデータを流し込み最低限の編集はできるようになりました。
・・・が、グリッドのHTML的な構造は見直す必要がありますよねぇ。

<div class="row body">
  <span class="cell"></span> ....延々とセルが続く
</div>

一つの要素(div.row.body)にすべてのセル(span.cell)を全部詰め込んで無理くりCSSでグリッド表示させてるだけなんで、<table>要素みたいに、行で分かれていないので、任意の行を選択したいときは、いちいち計算しないといけません。

ま、ちょっとだけグリッド表示したい、って時に使う用、と割り切る。

どうしても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 の心地よい起動サウンドが聞けるようになったとさ。おわり。

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

HTMLフォームでエンターキー押して入力要素のフォーカスを移動させたい、いや、しろよ!

2020年4月8日 間違い訂正を追記


HTMLでフォームをチマチマ組んでテストしていると、テキストボックス(input[type=text]要素)とかでついついエンターキー(改行キー)を押して、ムカッ!!!!!!とかしません? するよね、絶対するよね? 僕は自己嫌悪に陥って仕事を中断して帰りたくなる衝動に駆られます!

そこでググってjQueryのプラグイン探すなり、テック系の記事をコピペしたりしますよね。でも僕は探すより、自分で書く方が早いんですよ、結果的に。適当なjQueryプラグインなりを探してもそれの使い方を調べてたり、要らない機能があったりして、結局ドキュメントをよく読まないとわからないことが多い。

要は FORM要素内のINPUT要素に onkeyup  onkeydownイベントハンドラを仕込んでエンターキーをトラップすれば済む話。
たかだか数十行のスクリプトです。え?自分で組むとバグが!!!って? バグが出たらその都度直せばいいんです。ラクしちゃいけません。ラクするのはチラシの印刷だけでよろしい。

僕は jQuery 好き好き人間なので、適当にプラグインを書きました。

/********************************************************************
  filename: jquery.enterNext.js
  usage: call bellow...

   $('form').enterNext();

********************************************************************/
(function($,undefined)
 {
   $.enterNext =
     {
       config:
         {
           selectors: [
             'input[type="text"]',
             'input[type="password"]',
             'input[type="email"]',
             'input[type="number"]',
             'select',
             'textarea'
           ]
         }
     };

   var enterNextInternal = function(setting)
   {
     var selectors = setting.selectors.join(',');
     var $controls = $(selectors,this);
     var lastIndex = $controls.length - 1;
     var onKey =  function(ev) 
     {
       if(this.tagName.match(/input/i) && ev.keyCode == 13)
       {
         var currentIndex = $controls.index(this);
         var nextIndex = currentIndex + 1;
         if(currentIndex == lastIndex)
           nextIndex = 0;

         var nextObject = $controls.get(nextIndex);
         nextObject.focus();
       }
     };
     $(this).on('keydown',selectors,onKey);
     // 修正: onkeydown へ変更。onkeyupだと validityチェックでフォーカスが移動しなくなった。。。なんで?よくわからん。
   };
   
   $.fn.enterNext = function(options)
   {
     return this.each( function() { enterNextInternal.call(this,$.extend(true,{},$.enterNext.config,options)); });
   };
   
 })(jQuery);

で、これを以下のようなフォームで使うと、このままでは失敗して、エンターキー押すとフォームが送信されてしまいます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <style type="text/css"><!--
      form input[type=text] {
        display: block;
        margin: 1em 0;
        padding: 5px;
      }
    --></style>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script type="text/javascript" src="./jquery.enternext.js"></script>
  </head>
  <body>
    <div id="main">

      <!--メインコンテンツ-->
      <div id="contents">
        <div class="inner">
          <form method="post">
            <input type="text" name="a" value="" required />
            <input type="text" name="b" value="" />
            <input type="text" name="c" value="" />
            <input type="text" name="d" value="" />
            <input type="text" name="e" value="" />
            <input type="submit" value="送信">
          </form>
        </div>
      </div>

    </div><!-- #main -->
    <script type="text/javascript"><!--
      (function($) {
        $('form').enterNext();
      })(jQuery);

    //--></script>
  </body>
</html>

失敗の原因は、FORM要素内に input[type=submit]要素が存在するのが原因。
chromiumしか確認していないけど、どうやら input[type=submit]要素が存在すると、エンターキー押下で問答無用でFORMのアクションが走るようです。このinput[type=submit]要素を消すと送信されない、という挙動になってます。違ったらごめん。

追記:2020年4月8日
※ FORM要素内にinput[type=”text”]要素が一つしかない場合は、やっぱり問答無用でFORMのアクションが走るみたいです。嘘情報申し訳ない。上記はinput[type=”text”]が二つ以上ある場合のようです。一つしかないFORMの場合はダミーのinput[type=”text”]をどこかに配置して display: none; とかしとけばいいみたい。よく分からん仕様だなぁ。。。

じゃぁ、FORM要素にinput[type=button]要素なりa要素なりを書いて、そのonclickイベントハンドラ内で FORM要素のsubmitメソッドをコールすればいいんじゃない?ということになるんですが、残念ながらそうは問屋は降ろさない。
input[type=submit]要素がないと、FORM要素のvalidityチェックが走らないんだな、これが。せっかくinput要素に requireだのpatternだのを書いても無視されてしまう。

解決方法は2つ。

(1)
input[type=button]要素のonclickハンドラ内で、FORM要素のsubmitメソッドをコールする前に、reportValidityというメソッドをコールしてその返り値を判断して submitメソッドをコールするかどうかを決定する。

    <script type="text/javascript"><!--
      (function($) {
        $('form').enterNext();
        $('form [type=button]').click(function(ev) {
          if(this.form.reportValidity())
            this.form.submit();
        });

      })(jQuery);

    //--></script>

(2)
input[type=submit]要素を隠すか非表示(display: none;とか)にしておき、input[type=button]要素のonclickハンドラとFORM要素のonsubmitイベントハンドラを仕込んでおいてエンターキー押下かではなくクリックされたかをチェックさせる。

    <form>
    ....
    <input type="submit" style="display:none;" />
    <input type="button" value="送信" />
    </form>
    <script type="text/javascript"><!--
      (function($) {
        $('form').enterNext();

        $('form').submit(function(ev) {
          if(!'isClick' in this || this.isClick !== true)
          {
            ev.preventDefault();
            return false;
          }
        });
        $('form [type=button]').click(function(ev) {
          $('form').prop('isClick',true).find('[type=submit]').click();
        });

      })(jQuery);

    //--></script>

最初(2)の方を使ってたんだけど、(1)の方がカンタンなので、そっちにした。単に reportValidityというメソッドを後から知ったんだけどね💦

ちなみに、FORMのvalidityチェックの際、エラーメッセージをカスタマイズするプラグインもついで書いた。プラグインにするほどのものでもないけど、一回一回書くのはめんどっちーのでプラグインにした方がラク、というだけの理由ですけどね。

/******************************************************************
  Filename: jquery.validity.js
  入力必須の際のエラーメッセージをカスタマイズします。

   usage:
   <input type="text" pattern="^[0-9]*$" name="hogehoge" />

   $('input[type=text]').validity('半角数字で入力してください');
*******************************************************************/
;
(function($,undefined)
 {
   //デフォルト値
   $.validityMessage = 'この項目は入力必須です。';

   var validity = function(message)
   {
     if(message.length == 0)
       message = $.validityMessage;

     $(this)
       .on('invalid',function(ev) {
         if(this.validity.valueMissing || this.validity.patternMismatch || this.validity.typeMismatch)
         {
           this.setCustomValidity(message);
         }
         else
         {
           this.setCustomValidity('');
           ev.preventDefault();
         }
       })
      .on('input',function(ev) {
        this.setCustomValidity('');
      });
   };
   
   //plugin body
   $.fn.validity = function(message)
     {
       return this.each(function()
                        {
                          validity.call(this,message);
                        });
     };
   
 })(jQuery);