命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術についてのメモ。

Powershellで古い形式のExcel(xls)を新しい形式(xlsx)に変換する。

概要

下記の記事で作ったものと似たものを簡潔に書いてみました。

notshown.hatenablog.jp

実装

ソースコード

$ErrorActionPreference = "Stop" # 例外が出たらその時点で即終了
$srcDir = (Resolve-Path $args[0]).Path
$dstDir = (Resolve-Path $args[1]).Path
try{
    
    # Excelオブジェクト作成
    $excel = New-Object -ComObject Excel.Application
    $excel.Visible = $false
    $excel.DisplayAlerts = $false
    Get-ChildItem -Path $srcDir -Filter "*.xls" | % {
        $dstPath = Join-Path $dstDir $($_.BaseName + ".xlsx")
        if(-not (Test-Path -Path $dstPath)) {
            $book = $excel.Workbooks.Open($_.FullName)
            $book.SaveAs($dstPath, 51)
            $book.Close()
        }
        else {
        }
    }
} finally {
    $excel.Quit()
}

呼び出し方(Xls2Xlsx.ps1)

# 相対パス、絶対パス両方OKですが存在しないパスはできません。
Xls2Xlsx.ps1 .\dst .\src

今回試したこと

  • Try-Catch-Finally
  • Resolve-Pathによる相対パスの解決
  • Join-Pathによるパスの結合 (今まで[System.IO.Path]::Combineを呼んでいた…)
  • $ErrorActionPreference

感想

  • Resolve-Pathが存在しないパスだと例外出るので便利
  • PowerShellだとXlFileFormatに名前でアクセスできるから実装が楽で可読性も上がる
  • コマンド形式で書き直してみたい(そこらへん無知)

以上

エクセル内に定義された名前の重複を解決するマクロ

やりたいこと

以下ダイアログメッセージの連発を叩きのめす。

移動またはコピーしようとしている数式またはシートには、移動またはコピー先のワークシートに既にある名前 'foo' が含まれています。 この名前を使用しますか?
- コピーまたは移動先のシートに定義されている名前を使用する場合は、[はい]をクリックします。
- 数式またはワークシートで参照する範囲の名前を変更する場合は、[いいえ]をクリックし、[名前の重複] ダイアログボックスに新しい名前を入力します。

先にデメリット

すべて消えます。

この方法は定義された名前をすべて削除します。

そのため、セルに名前を付けているとすべて消えます

対象としてはまっさらにする時の用途なのでご注意ください。

たまに復活します。

古い形式(.xls)など、削除したはずが謎のタイミングで復活する場合もあります。

そうなると諦めて新しいファイルに書式などを丁寧にコピーするしかないです多分。

やること

開発」タブを表示するようにする。

(ファイル→オプション→リボンのユーザー設定)

「開発」タブの「マクロの記録」をクリック、OKをで初期表示の名前で記録開始。

「開発」タブの「記録終了」をクリック

「開発」タブの「マクロ」をクリック

マクロの記録で付けた名前を選択して、「編集(E)」をクリック

以下のような状態になっていると思います。

Option Explicit

Sub Macro1()
'
' Macro1 Macro
'

'
End Sub

全部消して以下をコピペする

Option Explicit

Sub Macro1()
'
' Macro1 Macro
'
Dim n As Name
Dim s As Worksheet

' ブックの範囲の名前を削除
For Each n In ThisWorkbook.Names
    n.Delete
Next

' シートの範囲の名前を削除
For Each s In ThisWorkbook.Worksheets

  For Each n In s.Names
    n.Delete
  Next
Next
msgbox "あいつはもう消した!"
'
End Sub

F5キーを押す

メッセージが出たら終わり。

以上!

2016年 C# 開発に使用しているNuGetまとめ

概要

早いもので今年ももうすぐ終わりということで(テンプレフレーズ)

C#でデスクトップアプリケーションを開発するのも慣れてきました(企業文化的な意味で)

ということで今年である程度まとまってきたC#でのデスクトップアプリケーション開発環境をメモ。

主にプロジェクトとNuGetで使用するライブラリです。

前提となる動作環境

  • Windows7以降
  • .Net 4.0以降
  • デスクトップアプリからSQL Serverとのやりとり。

プロジェクト

  • Livet (MVVM)
    WPFで開発する時はまずLivetプロジェクトを使っています。

https://marketplace.visualstudio.com/items?itemName=vs-publisher-129899.Livet-WPF445MVVM


NuGet

Dapper

対DBのオブジェクトマッパー。プロパティ名に日本語使ってもOKなので便利。

github.com

CsvHelper

CSVのオブジェクトマッパー。業務データがCSVの時にこれで読み込むととても楽。

joshclose.github.io

MahApps.Metro

WPFGUIをメトロスタイルにするやつ。自己満足でたまに導入します。

http://mahapps.com/

Json.NET

Jsonを読み込むライブラリ。

最近使い始めたけどちょっとした設定を外部ファイルにする時いいですね、json

www.newtonsoft.com

NLog

ロギングライブラリ。

出力先を社内共有サーバーにしてログを管理してます。

http://nlog-project.org/

QuickConverter

WPFの柔軟性の高いコンバーター

ちょっとbool反転したい時とかに式を書いてバインディングできます。ここ一ヶ月の中で感動したライブラリ。

いちいち実装しなくても、痒いところに手が届く!

quickconverter.codeplex.com

EPPuls

Excel 2010以降のファイルを高速に読み書き。

epplus.codeplex.com

NetOffice

古いExcelを読むならこれ。

netoffice.codeplex.com

だいたいこれらを使っていればちょっとした社内ツールの開発に十分かなという感じです。

以上!

VBScriptでOutlookのメールファイル(msg)から貼付ファイルを取り出す。

概要

複数のメールにある貼付ファイルを一気に取得したい!

ただし、OutlookVBAは勘弁な!ということで作りました。

このソースを ExtractAttachments.vbs みたいに保存して、

msgファイルをドラッグアンドドロップするとmsgファイルのディレクトリにモリモリ添付ファイルを取り出してくれます。

なお、元ファイルの名前の末尾に貼付ファイル名を追加するため、名前が重複することはありません。

ソースコード

Dim args, arg

' 許可されている拡張子
Set AllowedExtensions = CreateObject("Scripting.Dictionary")
AllowedExtensions.Add "msg"  , 0

Set args = WScript.Arguments

' 引数のチェック。対象ファイル以外が混ざっている場合終了。
set fobj = CreateObject("Scripting.FileSystemObject")
For Each arg In args
    ext = fobj.GetextensionName(arg)
    if not AllowedExtensions.Exists(ext) then
        msgbox "msgファイル以外が指定されました。終了します。"
        WScript.Quit
    end if
Next

Set appOL = CreateObject("Outlook.Application")
For Each path In args

    Set msg = appOL.CreateItemFromTemplate(path)
    for each atc in msg.Attachments
        atc.SaveAsFile path & "." & atc.DisplayName
    next
Next

msgbox "抽出完了しました。"

感想

やはりOffice系をいじるのはVBScriptが一番かなあと思う事が多いです。

VBScriptのステップ実行できるIDEがほしい。。。

以上!

C# - Chromeでダウンロードしているファイルの状況を確認するクラス

Seleniumからファイルのダウンロードリンクをクリックさせてダウンロードしたファイルをどうにかするプログラムの副産物

概要

Chromeはダウンロード中のファイルを以下の名前でダウンロードしていきます。

<元のファイル名>.crdownload

よって、そのcrdownloadファイルをうまいこと監視すればチェックしたいファイルのダウンロード完了を検出できるのです!
とか思ってたんですけど FileSystemWatcher って言う便利なクラスがあったんですね・・・。

もったいないので一応メモ。

ソースコード

    /// <summary>
    /// Chromeのダウンロードの状況クラス
    /// </summary>
    public class ChromeDownloadStatus
    {
        /// <summary>ダウンロードフォルダ</summary>
        public static readonly DirectoryInfo DownloadDirectory;

        /// <summary>ダウンロードフォルダの名前</summary>
        private static readonly string DownloadDirectoryName = "Downloads";
        /// <summary>キャッシュファイルの拡張子</summary>
        private static readonly string DownloadCacheExtension = ".crdownload";

        /// <summary>
        /// ダウンロード状態。
        /// </summary>
        public enum DownloadStatus
        {
            /// <summary>何もしていない</summary>
            None,
            /// <summary>ダウンロード中</summary>
            Downloading,
            /// <summary>ダウンロード完了</summary>
            Downloaded
        }

        /// <summary>
        /// 初期化。
        /// </summary>
        static ChromeDownloadStatus()
        {
            DownloadDirectory = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), DownloadDirectoryName));
        }

        /// <summary>
        /// 対象のファイルのダウンロード状況を取得する。
        /// </summary>
        /// <param name="fileName">対象のファイル名</param>
        /// <returns></returns>
        public DownloadStatus GetDownloadStatus(string fileName)
        {
            var cacheFile = new FileInfo(Path.Combine(DownloadDirectory.FullName, fileName + DownloadCacheExtension));
            var file = new FileInfo(Path.Combine(DownloadDirectory.FullName, fileName));

            // キャッシュがある場合はダウンロード中
            if (cacheFile.Exists)
            {
                return DownloadStatus.Downloading;
            }
            // キャッシュがなくて、ファイルが存在する場合は完了
            if (!cacheFile.Exists && file.Exists)
            {
                return DownloadStatus.Downloaded;
            }
            return DownloadStatus.None;
        }

        /// <summary>
        /// ダウンロード完了まで待つ。
        /// タイムアウトの場合はNULLを返す。
        /// </summary>
        /// <param name="fileName">ファイル名(ダウンロードフォルダに存在するもの。</param>
        /// <param name="loopWaitSpan">チェックごとのWait</param>
        /// <param name="timeout">タイムアウト時間。</param>
        /// <returns>ダウンロード完了したファイル情報</returns>
        public FileInfo WaitUntilDownloaded(string fileName, TimeSpan loopWaitSpan, TimeSpan timeout)
        {
            var start = DateTime.Now;

            // 指定のファイル名のダウンロード状況を取得する
            Func<DownloadStatus> getStatus = () => GetDownloadStatus(fileName);
            // タイムアウトかどうか取得する。
            Func<bool> isTimeout = () => timeout < (DateTime.Now - start);

            var dlStatus = getStatus();

            // 既にDL終了であればそのままファイルを返す
            if (dlStatus == DownloadStatus.Downloaded)
            {
                return DownloadDirectory.GetFiles(fileName).First();
            }

            // ダウンロード開始直前の場合開始していない扱いになるため開始になるまで待つ
            if (dlStatus == DownloadStatus.None)
            {
                while (getStatus() == DownloadStatus.None)
                {
                    Thread.Sleep(loopWaitSpan);
                    if (isTimeout())
                    {
                        return null;
                    }
                }
            }

            // ダウンロード完了状態になるまで待つ
            while (getStatus() != DownloadStatus.Downloaded)
            {
                Thread.Sleep(loopWaitSpan);
                if (isTimeout())
                {
                    return null;
                }
            }

            // ここまで来たらDL完了のため、ファイルを取得する
            return DownloadDirectory.GetFiles(fileName).FirstOrDefault();
        }

    }

Powershellで複数のCSVファイルを特定の列で1つのファイルにマージ

概要

以下2つのCSVを共通の列名"HeaderA"でまとめたCSVにしたい!

A.csv

HeaderA,HeaderB
ValueA,ValueB

B.csv

HeaderA,HeaderC
ValueA,ValueC

以下Powershellスクリプトで実現。

get-childItem "*.csv" | foreach {
    Import-Csv -Path $_ | Select HeaderA | Export-Csv -Append -NoTypeInformation -Path AB.csv
}

備考/感想

  • Selectに存在しないヘッダーを選んでも大丈夫(無いものは空文字になる)
  • 結果ファイルの全部のデータがダブルクォーテーションで囲まれて出力される。注意。
  • もちろんヘッダー指定は複数でもOK("Select HeaderA, HeaderB"という感じ)

社内ホームページにRiot.jsを導入してみた質の極めて低いメモ

前提

環境

  • 社内のホームページはホームページビルダーなどで作った静的なページ。
  • ホームページサーバーはWinServerでIISの標準的な機能でホストしている。

実装者

  • web系開発経験なし。全然ノウハウなし。
  • ウェブサービスのフロントエンドとバックエンドがどうデータをやり取りしているのかも分からないレベル。

契機

社内のホームページにある社内連絡のtable要素を非エンジニアがhtmlを直接編集しており、記述に難儀していた。 直接編集する作業をやめたい、という要望があった。

導入

全体像

  • 社内連絡の内容を記載し、マクロによってjson出力するExcelマクロ有効ブックを作る…①
  • 非エンジニアに①を使用させ、出力を行う…②
  • ホームページで②を読み込ませ、それを元にRiot.jsで特定のタグをマウントする。

簡単ですね。実際はVBAが実装面倒すぎて辛かったんですけど。

実装

jsonファイル

トップの"renraku"から、"date","text","url"の3つのデータを配列で持たせています。

{  
   "renraku":[  
      {  
         "date":"2016/11/30",
         "text":"おしらせ~",
         "url":"http://google.com"
      },
      {  
         "date":"2016/11/29",
         "text":"ファイルへのリンクお知らせ",
         "url":"files/お知らせ.xlsx"
      }
   ]
}

社内連絡のページHTML

カスタムタグではなくID指定で試してみた。

<!doctype html>
<html lang="ja">

<head>
  <title>社内連絡</title>
</head>

<body>
  <div id="riot-renraku-table"></div>
  <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
  <script type="riot/tag" src="tag/renraku-table.tag"></script>
  <script type="text/javascript" src="js/riot_compiler.min.js"></script>
  <script type="text/javascript">
    $.ajax({
      url: 'json/renraku.json',
      dataType: 'json',
      async: true,
      cache: false,
      success: function(res) {
        riot.mount('#riot-renraku-table', 'renraku-table', res.renraku);
      }
    });
  </script>
</body>

</html>

カスタムタグ

if文を使って、URLがあればa要素、なければdivという表示をしています。

<renraku-table>
  <table>
    <thead>
      <tr>
        <th>日付</th>
        <th>お知らせ</th>
      </tr>
    </thead>
    <tbody>
      <tr each={opts}>
        <td>{date}</td>
        <td>
            <a if={url!==""} href="{url}" target="_blank">{text}</a>
            <div if={url===""}>{text}</div>
        </td>
      </tr>
    </tbody>
  </table>
</renraku-table>

感想

なんか書くのが面倒になってしまったので感想でまとめて終わらそうと思うんですけど、
「なんかよくわからないが動いた」の一言につきます。

とりあえず初心者の手習いだと思っていただければ。