MozillaやThunderbirdもGroupPolicyで設定出来るらしい(覚え書き)

MozillaやThunderbirdもGroupPolicyで設定を制御できると聞いて色々と調べてたのだけど、事前にPluginをインストールしておく必要があると知って挫折。対象となるPCはそれほど多くないはずだけど、いまからPCに設定して歩くというのは、ちょっと非現実的にて。

ただ無償で使えるMUAの選択肢はそれほど多くなく、もしかしたらThunderbirdを展開する流れもあり得なくはない。機会があったら設定してみようと思う。

参考:https://addons.mozilla.org/ja/thunderbird/addon/gpo-support-for-firefox-and-th/

iRedMailでmailserverを構成してみた時の記録

iRedMailはPostfixの導入を初めてとして、Internet Mail Serverに必要な諸々の設定をWizardに答えるだけで行ってくれる便利なツールです。SMTP、POP3、IMAP4は当然として、Postfixの設定を行うためのWEBコンソールや、メールの送受信の為のWEBメール、ウィルス対策ソフトやスパムフィルタの設定までサポートしてくれます。セキュリティ上行うべき設定も一通りは自動で行ってくれるので、僕みたいな素人管理者には有り難いツールです。

使い方はとっても簡単。
iRedMail.shをダウンロードしたら、チュートリアルに従ってインストール先環境で実行してウィザードに答えていくだけです。

sudo bash iRedMail.shと起動して、ウィザードに従って操作していきます。
ウィザードは次の様な感じです。
・Welcomeメッセージ
・メールの保存先パス(デフォルトでOK)
・使用するDBMS(今ならMariaDBあたりで)
・DBMSのパスワード
・使用するWEBサーバ(今ならNginxあたりで)
・メールのドメイン名(@code-lab.netならcode-lab.netといれます)
・postmasterのパスワード
・一緒に導入するオプション(WEBメール等を選択します。デフォルトでOK。)
・・・・、と言ったことに、順番に答えていきます。
最後に設定の確認画面が表示されるので、yを入力します。
大量のパッケージが導入され、設定も適切に変更されていくので気長に待ちます。

この後にFirewallの設定を変更するかを聞いてきます。

< Question > Would you like to use firewall rules provided by iRedMail?
< Question > File: /etc/default/iptables, with SSHD port: 22. [Y|n]

[ INFO ] Copy firewall sample rules: /etc/default/iptables.
< Question > Restart firewall now (with SSHD port 22)? [y|N]

ここはデフォルトで進めます。

この後、再起動すると、動き出します。https://mail.example.com/iredadmin/に接続して、poastmasterのメールアドレス(postmaster@example.com)およびパスワードを入力すると管理画面にログイン出来ます。

最後にDNS回りの設定を行ってください。通常行うべきDNS設定も全てマニュアルとして提供されています。

でもね・・・実は最近のレンタルサーバ事業者の多くはポート25での外部接続を禁止しているので、メール送れないことにセットアップ後に気がついたりして。

GitBucketを4.23.1にUpgradeしたところjava.lang.NoSuchMethodError~Lscala/Tuple2が発生

しばらくバージョン放置気味だったGitBucketを4.23.1にUpgradeしつつ、きちんとTomcatから起動するように変更。

ところがログインして動かしていると至る所で「java.lang.NoSuchMethodError:gitbucket.core.helper.html.dropdown$.apply$default$5()Lscala/Tuple2;」が発生する。

随分と悩んだが原因は単純なところにあった。GitBucketに管理者でログインした後、SystemAdministrationにあるPuginsに異常なプラグインが。どうもデータベースの変換途中でエラーが発生したらしく「Notifications Plugin」が複数導入されている状態になっていた。重複した「Notifications Plugin」を削除したところ、正常に動作。

破損してマウントできないHDDからデータを復元する

起動中にブルースクリーン(UNMOUNTABLE_BOOL_VOLUME)になってしまうパソコンからのデータ救出。起動途中でブートパーティションを認識できなくなるために、エラーとなってしまいます。
試しに他のパソコンにHDDを接続して、CrystalDiskInfoで確認するとHDDで脱落セクタが発生しています。HDD内のパーティションは認識しているのですが、重要なセクタが保存不可能な状態になってしまったようで、ファイルシステムとして認識できずマウントできない状態になっています。
HDD復旧サービスを頼むと10万前後の支出になりますが、そこまで重要なデータが入って居る分けでもありません。HDDを認識しない(基盤故障など)、ディスクが回転しない(モーター故障)なんかの場合には個人では手も足も出ませんが、今回は認識しているし一部にはアクセス出来ています。そこで、個人で手が届く範囲で復仇作業を実施してみます。

何らかの方法で別のPCに接続

今回はHDDをUSBに接続するアダプタを使用。容量が大きいのでUSB3.0に対応している物を持っていると便利ですが、無ければ内蔵HDDとしてSATAに接続してしまうのがよい。

HDDのRAWデータを複製する

まずは物理HDDからRAWデータを読み出します。ここではHDDRawCopyと言うツールを使いました。このツールは破損領域があっても、CRCエラーを出力しながら読み込める範囲でRAWデータを複製してくれます。

HDDRawCopyPortableを起動して変換するHDDを選びます。HDDを選んだらContinueで進みます。

次に出力先としてFileを選びます。Fileをダブルクリックするとダイアログが開くのでファイル名を入力してください。ファイル液式はimgcではなく、imgを選択してください。出力先を決めたらContinueで進めます。

設定の確認画面になります。内容に間違いが無ければStartをクリックしてください。HDDのRAWデータがファイルに変換されていきます。エラーの状況が酷い場合には、変換にはそれなりに時間がかかります。数日単位になる場合も考えられますので、気長に待ってください。

RAWデータを仮想HDDに変換する

RAWデータのままではWindowsで扱うことが出来ません。Windowsでマウントできる仮想HDD形式である、vhd形式に変換します。
ここではqemu-img for Windowsを使いました。qemu-img.exeはqemuと言う仮想マシンに付属するディスクイメージのメンテナンスツールです。qemu-img.exe単体でもダウンロード出来ます。

qemu-imgをダウンロードして適当なフィルダに展開します。
展開したフォルダに移動して以下のコマンドを実行します。

qemu-img.exe convert -O vpc RawDiskImage.img RawDiskImage.vhd

これでvhd形式のファイルに変換されます。

Windowsから仮想HDDをマウント

出来上がった仮想HDDをWindwosでマウントしてしまうと、一部が書き替わってしまいます。作業前の状態に戻して再実行したいときに困るので、最初にvhdファイルを複製しておきましょう。

コントロールパネルの管理ツールから「コンピュータの管理」を起動します。

ツリーから「記憶域」のディスクの管理を選択したあと、「操作」から「VHDの選択」を選んでください。ここで先ほど作られたRawDiskImage.vhdを選択すると、HDDとしてマウントされドライブレターが追加されています。

今回はこの時点で既に読める状態に回復していましたので、chkdskで修復を試みたり、パーティション復元ツールで修復を試みたり、削除ファイル復元ツールで復元を試みたりと言った事は行っていません。必要なら、それらを実施して修復を試みてください。

ちなみにRAID5やRAID6を使っていた場合でも、HDDのRAWイメージを作成した後、ReclaiMe Free RAID recoveryのようなツールを使えば修復できる場合があります。そちらは今のところ試したことが無いけどね。

習作:FacebookのLikeボタンをクリックする

C#でSeleniumを用いて、Facebookのページ内にある「いいね!」を自動的にクリックするサンプル。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

using OpenQA.Selenium;

namespace LikeClicker
{
    class Program
    {
        static void Main(string[] args)
        {
            string userID = "test@example.co.jp";
            string password = "password";

            // AppSettings.BrowserName.Firefoxを変更することによって対象のブラウザを変更できます
            using (IWebDriver webDriver = WebDriverFactory.CreateInstance(AppSettings.BrowserName.HeadlessChrome))
            {
                webDriver.Url = @"https://www.facebook.com/";

                IWebElement userIdElement = webDriver.FindElement(By.Id("email"));
                IWebElement passwordElement = webDriver.FindElement(By.Id("pass"));

                userIdElement.SendKeys(userID);
                passwordElement.SendKeys(password);
                userIdElement.Submit();
                Console.WriteLine("認証が終了したら任意のキーを押して下さい。");
                Console.Read();

                string[] followURL = {
                        "https://www.facebook.com/pg/username1/posts/?ref=page_internal",
                        "https://www.facebook.com/pg/username2/posts/?ref=page_internal"};
                while (true)
                {
                    foreach (var tergetURL in followURL)
                    {
                        webDriver.Url = tergetURL;
                        Thread.Sleep(5000);

                        int findCount = 0;
                        foreach (var divElement in webDriver.FindElements(By.ClassName("_37uu")))
                        {
                            foreach (var refElement in divElement.FindElements(By.ClassName("UFILikeLink")))
                            {
                                if (refElement.GetAttribute("class").IndexOf("UFILinkBright") <= 0)
                                {
                                    refElement.Click();
                                    Thread.Sleep(3000);
                                }
                            }
                        }
                    }

                    Thread.Sleep(1000 * 60 * 10);
                }
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Safari;

namespace LikeClicker
{
    internal class WebDriverFactory
    {
        public static IWebDriver CreateInstance(AppSettings.BrowserName browserName)
        {
            switch (browserName)
            {
                case AppSettings.BrowserName.None:
                    throw new ArgumentException(string.Format("Not Definition. BrowserName:{0}", browserName));

                case AppSettings.BrowserName.Chrome:
                    return new ChromeDriver();

                case AppSettings.BrowserName.HeadlessChrome:
                    ChromeOptions option = new ChromeOptions();
                    option.AddArgument("--headless");
                    return new ChromeDriver(option);

                case AppSettings.BrowserName.Firefox:
                    FirefoxDriverService driverService = FirefoxDriverService.CreateDefaultService();
                    driverService.FirefoxBinaryPath = @"C:\Program Files (x86)\Mozilla Firefox\firefox.exe";
                    driverService.HideCommandPromptWindow = true;
                    driverService.SuppressInitialDiagnosticInformation = true;
                    return new FirefoxDriver(driverService);

                case AppSettings.BrowserName.InternetExplorer:
                    return new InternetExplorerDriver();

                case AppSettings.BrowserName.Edge:
                    return new EdgeDriver();

                case AppSettings.BrowserName.Safari:
                    return new SafariDriver();

                default:
                    throw new ArgumentException(string.Format("Not Definition. BrowserName:{0}", browserName));
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LikeClicker
{
    internal static class AppSettings
    {
        public enum BrowserName
        {
            None,
            Chrome,
            HeadlessChrome,
            Firefox,
            InternetExplorer,
            Edge,
            Safari
        }
    }
}

C# ODBC.NETでテーブルを複製するサンプル

DataReaderで取得したフィールド名等の情報を本に、SQL文(DELETE文、INSETR文)を動的に作成して、テーブル内のデータコピーを行うサンプル。

CopyTableの引数としてテーブル名、プライマリキー名の配列、コピー元データベースのConnection、コピー先データベースのConnectionを与えて下さい。

    class Program
    {
        static void Main(string[] args)
        {
            OdbcConnection srceConn = new OdbcConnection("Driver={SQL Server};Server=SV01;UID=example;PWD=;");
            srceConn.Open();

            OdbcConnection destConn = new OdbcConnection("Driver={SQL Server};Server=SV02;UID=example;PWD=;");
            destConn.Open();

            CopyTable("table_name", new string[] { "primary_key_1", "primary_key_2" }, srceConn, destConn);
            Console.ReadKey();
        }

        static void CopyTable(string tableName, string[] keyNames, OdbcConnection srceConn, OdbcConnection destConn)
        {
            Console.WriteLine("Copying {0}", tableName);

            StringBuilder insSql = null;
            Dictionary<string, int> columnIndexs = new Dictionary<string, int>();

            // DELETEコマンド文字列の生成
            StringBuilder delSQL = new StringBuilder();
            delSQL.Append(" DELETE ");
            delSQL.Append(tableName);
            delSQL.Append(" WHERE ");
            bool isFirst = true;
            foreach (var key in keyNames)
            {
                if (false == isFirst)
                {
                    delSQL.Append(" AND ");
                }
                delSQL.Append(key);
                delSQL.Append(" = ? ");
                isFirst = false;
            }

            // トランザクション開始
            var trans = destConn.BeginTransaction();

            // 複製本のデータ読み出し
            int recCnt = 0;
            OdbcCommand selectCmd = new OdbcCommand("SELECT * FROM " + tableName, srceConn);
            var rdr = selectCmd.ExecuteReader();
            while (rdr.Read())
            {
                Console.Write("record {0}\r", recCnt++);

                // フィールド位置検索用にインデックス作成
                if (columnIndexs.Keys.Count == 0)
                {
                    for (int i = 0; i < rdr.FieldCount; i++)
                    {
                        columnIndexs.Add(rdr.GetName(i), i);
                    }
                }

                // INSERTコマンド文字列の生成
                if (insSql == null)
                {
                    insSql = new StringBuilder();
                    insSql.Append(" INSERT INTO ");
                    insSql.Append(tableName);
                    insSql.Append(" ( ");
                    for (int i = 0; i < rdr.FieldCount; i++)
                    {
                        if (i > 0)
                        {
                            insSql.Append(",");
                        }
                        insSql.Append(rdr.GetName(i));
                    }

                    insSql.Append(" ) VALUES ( ");
                    for (int i = 0; i < rdr.FieldCount; i++)
                    {
                        if (i > 0)
                        {
                            insSql.Append(",");
                        }
                        insSql.Append("?");
                    }
                    insSql.Append(" ) ");
                }

                // DELETEコマンドのパラメータ設定と実行
                int keyCnt = 0;
                OdbcCommand delCmd = new OdbcCommand(delSQL.ToString(), destConn, trans);
                foreach (var key in keyNames)
                {
                    delCmd.Parameters.Add(new OdbcParameter("arg" + keyCnt.ToString("00"), rdr.GetValue(columnIndexs[key])));
                }
                delCmd.ExecuteNonQuery();

                // INSERTコマンドのパラメータ設定と実行
                keyCnt = 0;
                OdbcCommand insCmd = new OdbcCommand(insSql.ToString(), destConn, trans);
                for (int i = 0; i < rdr.FieldCount; i++)
                {
                    insCmd.Parameters.Add(new OdbcParameter("arg" + keyCnt.ToString("00"), rdr.GetValue(i)));
                }
                insCmd.ExecuteNonQuery();
            }

            Console.WriteLine("{0} Records Copyed...", recCnt);
            trans.Commit();
        }
    }

Windows 7からWindows 10への移行の難しさ

Windows 7からWindows 10への移行を、Windows XPからWindows 7への移行と同じように捉えていると、足下をすくわれる。Windows 10以降ではサポート体制や製品ライフサイクルが大きく見直されており、それに伴って従来のようにOSバージョンを固定にした塩漬け運用が難しくなっている。

Windows 10と、それ以前のMicrosoft製品との大きな違いは、OSの開発体制が移行したことにある。アジャイルデベロップメントとか、エクストリーム・プログラミングとか、継続的インテグレーションとか、ラピッドデベロップメントとか、ITの分野にいるなら聞いたことがあるはずだ。OS自体がアジャイルな開発体制に移行したことにより、Windows 10では、OSの新機能を何年かおきの新製品発売やサービスリリースを待たず、半年ごととのメジャーアップデートで提供する体制になった。

これに伴って大きく変更されたのが、アップデートを適用していないWindows 10に対するサポート提供だ。半年ごとに提供するメジャーアップデートを適用していない場合のサポート期限は18ヶ月と短い。Windows7 SP1の9年と比較しても極端に短い。

従来はOSのバージョンを固定にしたり、特定のサービスパックやWindows Updateの適用を避けることで、Windowsの機能更新にともなって表面化した不具合に対処していた場合がある。10より前は、それでもセキュリティアップデート等は提供され続けるので、OSのバージョンを塩漬けにしても問題なく運用できていたが、Windows 10以降では18ヶ月以内に対策を行うことが最低限もとめられる。

現状、新たにWindows 7を購入して使い続けているなら、相当な危機感を持って欲しい。20万円以上のパソコンの法定耐用年数は4年間、10~20万円以下なら3年間となる。2年以内に廃棄して特別損失を計上することを承知の上で、新たにWindows 7を買い続けているのは異常だ。

Windows 10の発売から既に2年以上経過し、Windows 7のサポート期限も分かっていたはずだ。にもかかわらずWindows 7にバージョンを固定し続けているというのは、Windows 10がメジャーアップデートするスケジュールに追従できるだけの開発能力が既に失われている事を示している。

自社開発していたソフトウェアを、市販のパッケージソフトウェアをそのまま使う形に置き換える事で、自社で開発するソフトウェアの数を減らす。あわせて、社内SE(特に開発寄りの)人材拡充をはかって、自社開発ソフトウェアの継続的保守開発に対応できる情報システム部署として再生をはかったほうがよい。

PS1.
Windows 10 Enterprise LTSC(Long-Term Servicing Channel)と言うランセンスを購入すれば10年間バージョンを固定にしてサポートを受けることが出来ます。一般的なProfessionalに比較して、1ライセンス辺り3万円ほど余計に支出することになりますけど。

PS2.
実はWindows 7 SP1で7年間バージョンを固定できたのは単に運が良かっただけ。もしWindows 7 SP2がリリースされていたなら、次期サービスパックリリースから24ヵ月でSP1はサポート終了していた。

参考:
Windows 7サポート終了まで2年 ユーザーはどうする?
OS にはサポート期限があります!
Windows ライフサイクルのファクト シート
https://internet.watch.impress.co.jp/docs/news/1073028.html

ブックの保護を強制的に解除する

【Excel】見積書の価格表を載せたシートは顧客に見られたくない!エクセルで特定のシートを非表示にしてパスワードをかけるテク

ブック保護のパスワードが分からなくなっても、強制的に解除することは可能です。

1. Excelのファイルをxlsxで保存する

2. ファイルの拡張子をxlsxからzipに変更する
実はxlsx形式のファイルは、zipファイルの中にxml形式のデータが格納したものです。

3. Explzhなどの解凍ツールを用いて開く

4. xl\workbook.xmlをメモ帳などで編集し「<workbookProtection~/>」を削除

5. zipファイルのxl\workbook.xmlを編集したもので置き換える。
必ず元のzipファイルに対して操作を行い、ファイルを置き換えてください。新規にzipファイルを作成して、拡張子をxlsxに変えても、プロパティ情報などの違いからxlsxファイルをは認識されません。

6. 拡張子をzipからxlsxに戻す

7. Excelでファイルを開く
Excelでファイルを開くときに、ファイルが壊れている旨の警告が表示されますが「はい」を選択して、ファイルを修復してください。これでパスワードが解除された状態になります。

BookやSheetの保護パスワードは、元データを暗号化しているわけではありません。もし暗号化していたら、Excelの式で参照することもできなくなりますからね。パスワードは暗号化されているので元のパスワードは分かりませんが、パスワードを設定するという属性ごと削除してしまえば、パスワードを解除できます。

内容を見たいだけなら「非表示のシート名!A1」といった式を書くだけでも、簡単に内容を確認できます。

顧客に見られても良い情報と、見られて困る情報を混在させる、良い方法というのはありません。仮にデータが暗号化されて保存される使用だったとしても、パスワードが漏洩している可能性だってあります。無駄にリスクを増やすよりも、素直に削除するなり、ExcelならPDF形式にエクスポートするなりといった方法を選びましょう。

PS…
zipファイルとして解凍してしまうテクニックは「複雑な正規表現置換をしたい」といった用途にも使えますので、知ってると便利です。

Outlook上で検索してもメールやメールアドレスが見つからない

Outlookの検索機能はWindows OSの提供するファイル検索機能を使用しています。何らかの理由でWindowsの検索用インデックスが破損していたり、あるいはインデックスの収集が行われていないと、Outlookの検索機能も動作しなくなります。

Windowsの検索機能はコントロールパネルの「インデックスのオプション」から設定出来ます。

「インデックスを作成する対象」のリストから「Microsoft Outlook」を選択して「詳細設定」のボタンを押して下さい。ここで「再構築」ボタンを押すと、既存のインデックスデータを破棄して再作成を開始します。

もしインデックスを作成する対象のリストに「Microsoft Outlook」が表示されていないなら「変更」ボタンをクリックして下さい。「インデックスが作成された場所の変更」のリストにチェックを付けると、インデックスを作成する対象としてリストに表示されます。

インデックスデータの再構築には暫く時間がかかります。半日ほど電源を入れたままにして様子を見て下さい。次第にきちんと検索にひっかかるメールの数が増えていくはずです。

Outlookを起動すると「このフォルダのセットを開けません。」と言うエラーになる

Outlookを起動したときに、下図のように「Microsoft Outlookを起動できません。Outlookウィンドウを開けません。このフォルダーのセットを開けません。予期しないエラーが発生しました。」というメッセージが表示されてOutlookが起動しなくなる場合があります。

これはOutlookの既定のpstファイルが壊れたり、あるいは設定中にエラーが発生したことにより既定のpstファイルが設定されていない事が原因でおこります。

コントロールパネルにあるユーザーアカウント→Mail(Microsoft Outlook ~)を開いてください。

すると次の様な画面が表示されるので、データファイルのボタンをクリックします。

次の様な画面が表示されます。既定に設定されているデータファイルが無い場合には、適当なデータファイルを選択して「既定に設定」して下さい。

既定に設定しようとしてもエラーとなる場合には、データファイル自体が壊れています。この場合には修復ツールを用いて、データファイルのエラーを修復します。修復するには「C:\Program Files\Microsoft Office\root\Office16」のフォルダにある、SCANPST.EXEを起動して、データファイルを選択します。

データファイルの大きさにもよりますが、修復には数時間~半日ほどかかります。急ぎ送受信だけでも可能にしたい場合、既存のデータは後日に修復してImportする物と割り切り、新規にデータファイルを作成して既定に設定する方が良いでしょう。