モルモルしている

最近はテスト業務ばっかりで書くことがない

SQLServer2016:メンテナンスプランを実行すると「ユーザー 'sa' はログインできませんでした。」のエラーログが出力される。

久々にハマって死にかけたので備忘録。

結論から言うと、SQLServer2016のメンテナンスプランをsaで実行すると、プラン内での処理の実行ごとに

ユーザー 'sa' はログインできませんでした。 理由: パスワードが、指定されたログインのパスワードと一致しませんでした。 [クライアント: ]

というログが出力され、ログイン失敗が回数制限されている場合saがロックアウトされることになります。(なりました↓)

ユーザー 'sa' はログインできませんでした。理由: アカウントは現在ロックされています。システム管理者はロックを解除できます。 [クライアント: ]

普段saしか使ってないのでsaがロックアウトとか死んだ、と思ったけど何とかなった。

システム管理者がロックアウトされた場合の SQL Server への接続 | Microsoft Docs
ここの記事を参考にして、クエリウィンドウになんとか接続。

SQL Server 2005 Management Studio の [ログインをロックアウトする] チェックを外した際の注意点について – Microsoft SQL Server Japan Support Team Blog
ここの記事を見て

ALTER LOGIN [Mary5] WITH PASSWORD = '****' UNLOCK ; 

これを実行してsaのロックアウトの解除に成功。
とはいえ、saのパスワードはあってるし何も問題ないのにジョブを実行した瞬間からエラーが出まくって挙句ロックアウトされる意味が分からず調べたけどよくわからなかった。
が、どうやらSQLServer2016のバグっぽいです。

Maintenance plan - Login failed for user 'sa' on master database
dba.stackexchange.com

回避方法としては、sa認証ではなくWindows認証を使えば不正なログも出力されず正常に実行されるとのこと。
(確かにWindows認証に変更したら正常に実行できました)

アイロンビーズで小物入れを作ったよ

この記事は アイロンビーズ Advent Calendar 2015 - Adventar の19日目の記事です。

初めまして、モルと申します!
普段は皆様の作品を鑑賞させていただくばっかりなのですが、せっかくなので季節感とかないのですがこの冬作った小物入れをご紹介させていただきます。

というわけでこんな感じの三段の引き出しがついた小物いれを作成しました。
f:id:MoruMe:20151219220033j:plain

各パーツはテグスでできるかぎり固定してあるので多少物を入れても外れちゃったりはしないはずです。
でも見た通り下段の棚板を一マス間違えてしまって、一番下の引き出しはやたら余裕があるのに真ん中の引き出しはみちみちに収まっているという状態になってしまいました…。
脳内設計書だけでやるからこんなことになるとはわかっているものの、設計書を書くのは苦手です。。。

設計も難しいけど、アイロンかけるのも難しいですね。
途中で壊滅的に崩れて投げだしそうになったりもしましたが…
f:id:MoruMe:20151219133846p:plain

なんとか完成にこじつけられてよかったです。
ちなみに引き出しの大きさはエネループの充電器が入る程度。
f:id:MoruMe:20151219134355p:plain

普段からこうやって小物入れとか生活をする中でちょっとした入れ物がほしいときとかにアイロンビーズを使っています。
アイロンをかけるとやり直しがきかない部分は難しいけど、自分で好きな形、見た目の箱やらなんやら作れるのは便利だし楽しいですね。

ちなみにアイロンビーズの作品をお披露目するのは初めてなのでこのブログの記事を遡っても特にアイロンビーズに関する記述はありませんw

EPPlusで列幅を自動で設定する

いや、まぁEPPlusのサンプルに書いてあるんですけど備忘録的に書いとく。

端的に言うと
「ExcelWorksheet.Cells.AutoFitColumns」
を使います。

テストコード(ボタンを押すとテストテーブルに入ってるデータをただExcelに出力するサンプル)

private void button1_Click(object sender, EventArgs e)
{
    // 出力ファイルのパス
    string output = @"C:\temp\hoge.xlsx";

    // ファイルの作成
    FileInfo newFile = new FileInfo(output);

    if (newFile.Exists)
    {
        newFile.Delete();
        newFile = new FileInfo(output);
    }

    using (var cn = new SqlConnection())
    {
        cn.ConnectionString = "Server=TestSrv;Database=TestDb;UID=sa;PWD=TestPass;";
        cn.Open();

        using (var package = new ExcelPackage(newFile))
        {
            // Sheetの追加
            ExcelWorksheet ws = package.Workbook.Worksheets.Add("Sheet1");

            // テーブルデータの取得
            var a = cn.ExecuteReader("SELECT * FROM testTable");

            // テーブルデータをセル「A1」から書き出し
            using (ExcelRange row = ws.Cells["A1"])
            {
                var cnt = a.FieldCount;
                var r = row.Start.Row;
                var c = row.Start.Column;

                while (a.Read())
                {
                    for (int i = 0; i < cnt; i++)
                    {
                        ws.Cells[r, c + i].Value = a[i];
                    }
                    r += 1;
                }
            }

            // セル幅を自動調節に設定する
            ws.Cells.AutoFitColumns(8);

             // 作ったデータをバイト配列にセット(日本語があってるか不明)
            Byte[] bin = package.GetAsByteArray();
            // ファイルを出力
            File.WriteAllBytes(output, bin);
    }

    MessageBox.Show("完了しました。");
    }
}

こんな感じ。


EPPlusのSample1だとAutoFitColumnsの引数は0ですが、0にしてるとデータがなかった場合
f:id:MoruMe:20151016222921p:plain

こんな感じにデータがない列がつぶれます。
データ出力しなかったセルは列幅の影響受けてません。

AutoFitColumnsの引数を8にすると
f:id:MoruMe:20151016222950p:plain

こんな感じ。8よりちょっと小さくなりました。
データに合わせて頃合いな数字を調整すればいい感じ。

SteamでPS3のコントローラーを認識させる方法

最近は仕事も開発から離れてて書くことないからゲームのこと書くよー。

Steamでゲームやるのに、PS3のアナログコントローラー使えると知ってですね。
設定してたわけですよ。最近はPS4ばっかやっててPS3のコントローラ空いてるしな。

PS3コントローラーをPCに接続する方法 | PS3のコントローラー

↑を参考にさせていただいてですね。
基本的には別に問題なく進んでたんだけど、MotioninJoy Gamepad tool起動して、DriverManagerからインストールする段になって行き詰った。
Install allをクリックしたらインストールされて終わりのはずが。

f:id:MoruMe:20150916213518p:plain

WARNING:DRIVER_PACKAGE_LEGACY_MODE flag set but not supported on Plug and Play driver on VISTA. Flag will be ignored.

ちょっと何言ってるかわからなかったのでGoogle翻訳に聞いてみた。

警告: DRIVER_PACKAGE_LEGACY_MODEフラグが設定されますが、プラグインでサポートされていないとVistaにドライバを再生します。フラグは無視されます。

やっぱりわからなかった。
オーケー、理解するのはあきらめよう。
ってことでメッセージぐぐった。

あったよ。解答。www.youtube.com
システム日付を2014年に変えればインストールできるよ!って内容なんだけど、なぜこれでうまくいくかはわからない。
でもシステム日付変えたらインストールできたし、動いたよ。
もし困ってる人いたらやってみてね。

EPPlusで表をExcelに出力する

要件:
テーブルにストアド名と出力シート名、出力開始セル名を持っているので設定されたストアドを実行し、得た結果を指定されたシート名、セルに出力しなさい。
※ただし実行環境にExcelはインストールされていません。



(´・ω・) 無理っす!



と思ったが、同僚からEPPlusというものがあると教えてもらってなんとかなりました。
これ今後何回もやることになりそうなので作ったサンプルをまるまま残しておこう。
ちなみにSQLServerとのやり取りはDapperを使っています。
(あ、ちなみにEPPlusのライセンスはLGPLです)

  • やること
  1. NuGetでEPPlusを検索し、インストール
  2. using OfficeOpenXml; using OfficeOpenXml.Style; を追加
  3. コードを書く
    1. 設定テーブルから実行するストアドと出力先情報をリスト(あらかじめ定義しておいたクラス)に取得
    2. 取得した設定ごとにストアドの実行(ストアドは実行した結果値を返す=SELECTするものとする)
    3. 得たデータを指定のシートの指定のセルを開始位置として出力する
    4. 出力ファイルは指定のテンプレートを使って、指定の場所へ指定の名称で保存。
private void button1_Click(object sender, EventArgs e)
        {
            // テンプレートファイル
            FileInfo template = new FileInfo(@"C:\Work\Template.xlsx");
            // 出力先
            string outputdir = @"C:\Work";

            using (var cn = new SqlConnection())
            {
                cn.ConnectionString = "Server=TestServer;Database=TestDB;UID=sa;PWD=sa;";
                var list = new List<storedSet>();

                // ストアド設定をリストで取得
                list = cn.Query<storedSet>("SELECT * FROM StoredSetting").ToList();

                // ここから出力作業
                using (var package = new ExcelPackage(template, true))
                {
                    // 取得したストアド一つごとに
                    foreach (var set in list)
                    {
                        // 出力先シートは名称で設定しているのでIndexをとってくる
                        var index = GetSheetIndex(set.SheetName, template);
                        ExcelWorksheet ws = package.Workbook.Worksheets[index];

                        // 出力開始セルをRangeに設定
                        using (ExcelRange row = ws.Cells[set.CellName])
                        {
                            // ストアドを実行
                            var a = cn.ExecuteReader(set.StoredProcedure, commandType: System.Data.CommandType.StoredProcedure);

                            // ストアドから得た結果のフィールド数を取得
                            var cnt = a.FieldCount;

                            // 開始セルの位置を数値で取得
                            var r = row.Start.Row;
                            var c = row.Start.Column;

                            // 読み込んだ結果を一行ずつ読み込んで出力
                            while (a.Read())
                            {
                                for (int i = 0; i < cnt; i++)
                                {
                                    ws.Cells[r, c + i].Value = a[i];
                                }
                                r += 1;
                            }
                            a.Close();
                        }
                    }
             
                    // 作ったデータをバイト配列にセット(日本語があってるか不明)
                    Byte[] bin = package.GetAsByteArray();
                    // ファイルを出力
                    string file = outputdir + @"\Dekita.xlsx";
                    File.WriteAllBytes(file, bin);
                }
            }

            MessageBox.Show("出力完了");
        }

        private int GetSheetIndex(string sheetName,FileInfo filePath)
        {
            using (var package = new ExcelPackage(filePath))
            {
                using (var stream = File.OpenRead(filePath.FullName))
                {
                    package.Load(stream);
                }

                int i = 0;
                foreach(var sh in package.Workbook.Worksheets)
                {
                    if (sh.Name == sheetName)
                    {
                        return i + 1;
                    }
                    i += 1;
                }
            }
            return -1;
        }
    }

    public class storedSet
    {
        public int Id { get; set; }
        public string StoredProcedure { get; set; }
        public string SheetName { get; set; }
        public string CellName { get; set; }
    }

参考URL:
C# - EPPlusを使ったExcel Hello World - Qiita
C#でエクセルシートへアクセス
c# - Get Cell's Row number using EPPlus - Stack Overflow

シートのIndexを取得するところはまるまま二番目のサイト様のやり方を使わせてもらった。助かります。

コンテキストメニューから「印刷」を削除する

一応気を付けてはいるんだけどちょっとしたクリックミスで印刷なんか実行しちゃってがっかり。
そもそも右クリックに印刷があること自体どうなのっていう。
ファイルを開かないで印刷するとかどんな時に発生するのよ。

いちいちこんなことのためにフリーソフトとか入れて設定するのもいや。
調べたらレジストリの変更で消せることが分かったので今後のためにメモ。

下記のレジストリに文字列値のキーを新規追加して名称を「LegacyDisable」にするだけ

txtファイル
[HKEY_CLASSES_ROOT\txtfile\shell\print]
[HKEY_CLASSES_ROOT\txtfile\shell\printto]

word 2013
[HKEY_CLASSES_ROOT\Word.Document.12\shell\Print]
[HKEY_CLASSES_ROOT\Word.Document.12\shell\Printto]

excel 2013
[HKEY_CLASSES_ROOT\Excel.Sheet.12\shell\Print]
[HKEY_CLASSES_ROOT\Excel.Sheet.12\shell\Printto]

batファイル
[HKEY_CLASSES_ROOT\batfile\shell\print]

cmdファイル
[HKEY_CLASSES_ROOT\cmdfile\shell\print]

参考URL:
右クリックメニューから「印刷」なんかしねぇよ! - 「ん」の失踪


設定した瞬間から対象ファイルを右クリックして表示されたコンテキストメニューには「印刷」がない。
CSVは開くのにEXCEL使わないから探すのにちょっと手間取った。
でもこれで普段使うファイルを誤って印刷するケースは軽減できるかな。

EXECで実行したスクリプトの結果を変数に得たい

クエリ自体は動的に作成したいんで文字列として生成してEXECで実行したいんだけど、そのクエリの結果は変数に取得したいんです。
こういう場合、EXECではだめなのだそうです。

目的:
 テーブル名を動的に変えて値を取ってきたいです。
 指定されたテーブルのDATANAMEというカラムに「Result」と入ったレコードの「Value」というカラムの値がほしいです。

-- 取得した値を格納する変数
DECLARE @GetResult nvarchar(50) = ''
-- 言わずもがなテーブル名
DECLARE @TableName nvarchar(50) = 'SampleTable'
-- 抽出用クエリ
DECLARE @Query nvarchar(500) = 'SELECT @value = Value FROM ' + @TableName + 'WHERE DATANAME = ''Result'''
-- 引数のための変数
DECLARE @Param nvarchar(50) = '@value nvarchar(50) OUTPUT'

-- 実行
EXECUTE sp_executesql @Query, @Param, @value = @GetResult OUTPUT

-- 確認
SELECT @GetResult

こんな感じにすればEXECで実行しようとしてたクエリで取得する値が変数に入ります。

参考URL:
 http://www.ilovex.co.jp/blog/system/softwaredevelopment/transact-sqlsql.html