tesseractでOCR@Windows7

OCRに興味があったので試してみた.

tesseract

OCRエンジンのオープンソース. https://github.com/tesseract-ocr/tesseract

tesseractのインストール

Windows7でtesseractを使えるようにした. まず, https://github.com/parrot-office/tesseract/releases/tag/3.5.1 からtesseract-Win64.zipをダウンロードした. ダウンロードしたらディレクトリ(C:\Program Files\Tesseract-OCR)を作成して, zipファイルをC:\Program Files\Tesseract-OCRに展開した.

辞書データの準備

https://github.com/tesseract-ocr/tesseract/wiki/Data-Files のeng.traineddata, jpn.traineddataをダウンロードする. ディレクトリ(C:\Program Files\Tesseract-OCR\tessdata)を作成して, eng.traineddata, jpn.traineddataを置く.

環境変数の設定

変数
Path C:\Program Files\Tesseract-OCR
TESSDATA_PREFIX C:\Program Files\Tesseract-OCR\tessdata

tesseractを試す

コマンドプロンプトでtesseractを実行する. sample.pngをtesseractでテキストにしてみる.

tesseract sample.png result -l jpn

画像

sample.png

結果 result.txt

何言つてんだよ! !
その崖っぷちが最髙のチヤンスなんだぜ ! !

まつおかしゅうぞう

ちゃんとテキスト化できた(笑)

pyocr

tesseractをpythonから使えるようにしたラッパー. https://gitlab.gnome.org/World/OpenPaperwork/pyocr

pyocrのインストール

環境はanaconda@windows7. pyocr用に仮想環境を作ってpyocrをインストールする.

> conda create -n ocr python=3.6
> conda info -e
# conda environments:
#
base                  *  c:\tools\Anaconda3
ocr                      c:\tools\Anaconda3\envs\ocr
> activate ocr
(ocr)> pip install pyocr

pyocrからtesseractを呼び出せるか確認

https://gitlab.gnome.org/World/OpenPaperwork/pyocr のUsage Initializationで検証する.

test.py

from PIL import Image
import sys

import pyocr
import pyocr.builders

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
# The tools are returned in the recommended order of usage
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))
# Ex: Will use tool 'libtesseract'

langs = tool.get_available_languages()
print("Available languages: %s" % ", ".join(langs))
lang = langs[0]
print("Will use lang '%s'" % (lang))
# Ex: Will use lang 'fra'
# Note that languages are NOT sorted in any way. Please refer
# to the system locale settings for the default language
# to use.
(ocr)> python test.py
Will use tool 'Tesseract (sh)'
Available languages: eng, jpn
Will use lang 'eng'

tesseractが正しく認識されているようだ.

pyocrを使ってみる

img_to_txt.py

from PIL import Image
import sys
sys.path.append('/path/to/dir')

import codecs
import pyocr
import pyocr.builders

tool = pyocr.get_available_tools()[0]
builder = pyocr.builders.TextBuilder()

# 画像から文字列を取得する
txt = tool.image_to_string(
    Image.open('sample2.png'),
    lang='jpn',
    builder=builder
)

# 文字列を書き出す
with codecs.open("result2.txt", 'w', encoding='utf-8') as file_descriptor:
    builder.write_file(file_descriptor, txt)
(ocr)> python img_to_txt.py

画像

sample2.PNG

結果 restult2.txt

ー番になるつていつたよな?
日本ーなるつつつたよな!
ぬるま湯なんかつかつてんじゃねぇよお前! !

まつおかしゆうぞう

アツイ言葉が画像から読み取れた(笑)

今後

とりあえず軽く試してみて思った以上に使えそうなことが分かった. 次は手書きの文字でもちゃんと認識できるのか試したい.

GACからDLLをエクスポートする

GACからDLLをエクスポートする

GACからDLLをエクスポートする必要があったため普通無い調べた.

GACって?

  • Global Assembly Cacheのこと.
  • マシンでグローバルにアセンブリを参照出来る.
  • 規定のパスは「%windir%\assembly」.

GACをのぞく

エクスプローラーで「C:\Windows\assembly」を開いてみる.

f:id:mt9116:20180628102651p:plain

通常の表示とは若干異なる. これは「アセンブリキャッシュビューア」によるものらしい.

f:id:mt9116:20180628102659p:plain

アセンブリキャッシュビューアが有効だとDLLがコピーできないみたい. これじゃ困るのでこれを無効にする.

アセンブリキャッシュビューアを無効にする

regedit.exeを管理者権限で起動する. 「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion」で右クリックし「新規」→「DWORD(32ビット)」をクリックする.

f:id:mt9116:20180628102708p:plain

名前を「DiableCacheViewer」とする. 「DisableCacheViewer」を右クリックし, 修正をクリックする.

f:id:mt9116:20180628102716p:plain

「値のデータ」を「1」とし, OKをクリックする.

f:id:mt9116:20180628102725p:plain

これでアセンブリキャッシュビューアが無効になる.

f:id:mt9116:20180628102733p:plain

エクスプローラーで「C:\Windows\assembly」を開いてみるとアセンブリキャッシュビューアが無効になったことが分かる.

f:id:mt9116:20180628102741p:plain

GUIでやるのがめんどくさい人はPowerShelでどうぞー

 New-ItemProperty -path "HKLM:\Software\Microsoft\Fusion" -name "DisableCacheViewer" -value 1 –PropertyType DWORD -Force

GACからDLLをエクスポートする

アセンブリキャッシュビューアを無効にすればあとは好きなだけDLLをコピーできる.

f:id:mt9116:20180628102749p:plain

Copy-Item -Path C:\Windows\assembly\GAC_32\ESRI.ArcGIS.ADF -Destination D:\Somewhere -Force -Recurse

裏話

ArcGIS 10.1から10.6にアップグレードした際にジオコーディングバッチが動かなくなった. ログを見るとDLLの参照がありません的なメッセージがあった. そこでバッチで参照しているDLLを見てみるとArcGIS Web ADFなるものを発見した. ArcGIS Web ADFについて調査すると...

次期バージョンの ArcGIS 10.2 がいよいよ来月末リリースする予定です。前回のバージョン 10.1 のリリースが 2012 年 10 月 1 日でしたのでちょうど 1 年の歳月を経てのバージョンアップとなります。 この ArcGIS 10.2 のリリースに伴い、ArcGIS 10.1 までサポートされていたいくつかのコンポーネントや環境などがサポート終了となります。 目立ったところでは、ArcGIS for Server に付属していた Web アプリケーション開発キットの Web ADF(Application Developer Framework)および ArcGIS Server Manager のウィザードを使って作成できる Web マッピング アプリケーションが提供されなくなります。これらについてはすでに JavaScriptFlex、そして Silverlight といった各種 Web API を提供しており、それらを使用することをお勧めしています。

そりゃあ10.6で提供されているわけないよなー ということで旧サーバーからDLLを取り出す必要があったためこの記事を書くに至った. バッチをビルドするときにDLLも含めておいてくれれば済んだ話なのに

差分バックアップのリストアにはまった話

差分バックアップのリストアにはまった

DBサーバーリプレースの話. 本番環境が稼働している裏でDBサーバーを構築し, 本番環境に切り替えるタイミングで差分バックアップを取得して構築したDBサーバーにリストアしようと画策したらちょっとはまった.

本番環境で稼働しているDBサーバーを「旧DBサーバー」, 裏で構築しているDBサーバーを「新DBサーバー」とする. 使っているDBは旧DBサーバーが「SQL Server 2008 R2 Standard」, 新DBサーバーが「SQL Server 2016 Enterprise」.

事前準備

新DBサーバーを構築してフルバックアップを適用する.

旧DBサーバーでフルバックアップを取得する.

BACKUP DATABASE [SAMPLE] TO  DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak';

新DBサーバーにフルバックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak';

差分バックアップを取得してリストアする

挑戦その1

旧DBサーバーで差分バックアップを取得する.

BACKUP DATABASE [SAMPLE] TO  DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak' WITH DIFFERENTIAL;

新DBサーバーに差分バックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak';

するとこんなエラーが

メッセージ 3117、レベル 16、状態 1、行 1
ロールフォワードできる状態のファイルがないので、ログまたは差分バックアップは復元できません。
メッセージ 3013、レベル 16、状態 1、行 1
RESTORE DATABASE が異常終了しています。

なんだと...

原因調査

データベースの状態を見てみる.

SELECT DATABASEPROPERTYEX('SAMPLE', 'Status') AS DB_STATUS;
DB_STATUS
---------
ONLINE

デフォルトでリストアするとステータスがONLINEになり, 差分バックアップのリストアを受け付けなくなってしまうようだ. まあ, ONLINEだとデータベース操作できるからデータに不整合生じて差分バックアップをリストア出来なくなるのも当然か.

解決策

リストアのオプションを調べた.

オプション 内容
RECOVERY デフォルトオプション. リストア後データベースは「完全復旧」状態となる. 以降バックアップのリストアを受け付けない.
NORECOVERY リストア後データベースは「復元中」状態となる. データベースの操作を一切受け付けない.
STANDBY リストア後データベースは「スタンバイ」状態となる. 読み取りのみ可能にするためデータの不整合が生じない.

新DBサーバー構築後, 各アプリケーション, 地図サーバーから接続出来る状態にしておきたいので出来ればSTANDBY状態でフルバックアップをリストアしておきたい. というわけで, フルバックアップをSTANDBYでリストアして差分バックアップをリストア出来るか試してみる.

挑戦その2

一旦新DBサーバーのデータベースを消す.

DROP DATABASE [SAMPLE];

新DBサーバーにフルバックアップをSTANDBYでリストアするぞ!!

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak'
WITH STANDBY = N'E:\SAMPLE\Backup\SAMPLE_ROLLBACKUNDO.bak';

結果だめだった...

メッセージ 3180、レベル 16、状態 1、行 1
このバックアップは WITH STANDBY では復元できません。データベースのアップグレードが必要です。WITH STANDBY を指定しないで RESTORE を再実行してください。
メッセージ 3013、レベル 16、状態 1、行 1
RESTORE DATABASE が異常終了しています。

SQL Server 2008 R2 Standard」で取得したフルバックアップを「SQL Server 2016 Enterprise」にSTANDBYでリストア出来ないみたいっす. ちなみに「SQL Server 2016 Enterprise」で取得したフルバックアップを「SQL Server 2016 Enterprise」にSTANDBYでリストアすることは出来た. 闇が深そう(そもそもだめなのかな...)なので別の策を考えることに.

結局

残りはNORECOVERYしかないんでフルバックアップをNORECOVERYでリストアして差分バックアップをリストア出来るか試す.

最後の挑戦

新DBサーバーにフルバックアップをNORECOVERYでリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak'
WITH NORECOVERY;

新DBサーバーに差分バックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak';

正常終了した. データ見ても差分バックアップの内容が反映されていた.

まとめ

データベースをリストアする際はオプションに気をつけよう.

PowerShellでWebサーバーのセキュリティ対策を自動化した話

PowerShellでWebサーバーのセキュリティ対策を自動化した

旧サーバーで実施したセキュリティ対策を新しく構築するサーバー(2台)でも実施する必要があった. 対策を手で2台もやるのはめんどくさい大変なのでスクリプトで自動化した.

実現したこと

  • SSL証明書をインポートし, HTTPS通信を有効にする.
  • レジストリを編集し, 脆弱な暗号化方式を無効にする.
  • セキュリティ診断対応済みの設定ファイルをIISのルートディレクトリに配備し, 以下の脆弱性を解消する.
    • セキュリティ上推奨されるHTTPヘッダーが付加されるようにする.
    • デフォルトのページを非表示にするため, 規定のドキュメントを無効にする.
    • ディレクトリリスティングを無効にする.
  • urlscanをインストールし, 推奨されないHTTPメソッドを無効にする.

スクリプト

構成

ResolveVulnerability
┣Functions
┃┣Deploy-WebConfig.ps1
┃┣Disable-Encryption.ps1
┃┣Enable-SSL.ps1
┃┣Install-UrlScan.ps1
┃┣Remove-IISDefaultItems.ps1
┃┗Restart-IIS.ps1
┣Certificate
┃┗certificate.pfx
┣Config
┃┣UrlScan.ini
┃┗web.config
┣Installer
┃┗urlscan_v31_x64.msi
┗Script
  ┣Config.ps1
  ┣Index.ps1
  ┗Resolve-Vulnerability.ps1

実行方法

管理者権限でPowerShellを起動して下記コマンドを実行するだけ.

Set-Location C:\ResolveVulnerability
.\Resolve-Vulnerability.ps1

ただ, インポートするSSL証明書は事前に用意してね.

中身

Resolve-Vulnerability.ps1でFunctions内の関数を呼ぶ感じ. Resolve-Vulnerability.ps1ではIndex.ps1を読み込む.

Resolve-Vulnerability.ps1

. ("{0}/Index.ps1" -f (Split-Path $MyInvocation.MyCommand.Path -Parent))

# SSL証明書をインポートし, https通信を有効にする
Enable-SSL -CertPath $CertPath -CertPass $CertPass -TargetSite $TargetSite

# 脆弱な暗号化方式を無効化する
# Protocols
Disable-Encryption -Key $SSL2Server 
Disable-Encryption -Key $SSL2Client 
Disable-Encryption -Key $SSL3Server 
Disable-Encryption -Key $SSL3Client 
# Cipher
Disable-Encryption -Key $CiphersNULL 
Disable-Encryption -Key $DES 
Disable-Encryption -Key $RC2_40 
Disable-Encryption -Key $RC2_56 
Disable-Encryption -Key $RC2_128 
Disable-Encryption -Key $RC4_40 
Disable-Encryption -Key $RC4_56 
Disable-Encryption -Key $RC4_128 
Disable-Encryption -Key $RC4_64 
Disable-Encryption -Key $3DES 
# Hashes
Disable-Encryption -Key $MD5 
# KeyExchangeAlgorithms
Disable-Encryption -Key $DiffieHellman 

# レジストリの設定を有効にするためIISを再起動する
Restart-IIS $IISSvcName

<# IISのルートディレクトリにWeb.configを配備することで下記セキュリティ対策を実施する
    ・セキュリティ上推奨されるHTTPヘッダーを付加する
    ・規定のドキュメントを無効にする(デフォルトのページが非表示になる)
    ・ディレクトリリスティングを無効にする #>
Deploy-WebConfig -WebConfigSrc $WebConfigSrc -IISRoot $IISRoot -WebConfigFile $WebConfigFile

# デフォルトのiisページを消す
Remove-IISDefaultItems $IISDefaultItems

# urlscanインストールして設定ファイル置換する
Install-UrlScan -UrlScanInstaller $UrlScanInstaller -UrlScanConfigSrc $UrlScanConfigSrc -UrlScanConfigDst $UrlScanConfigDst -UrlScanConfigFile $UrlScanConfigFile

Index.ps1ではConfig.ps1とFunctions内のスクリプトを一括で読み込む.

Index.ps1

Set-Variable -Name BVIndexRoot -Value (Split-Path $MyInvocation.MyCommand.Path -Parent) -Option Constant

# 設定ファイル
. ("{0}\Config.ps1" -f $BVIndexRoot)

# 関数
Set-Variable -Name BVFunctionsRoot -Value ("{0}\Functions" -f $BVIndexRoot) -Option Constant
Get-ChildItem -Path $BVFunctionsRoot | ForEach-Object { . ("{0}\{1}" -f $BVFunctionsRoot, $_)}

Config.ps1に設定をまとめる.

Config.ps1

# スクリプトのパス
Set-Variable -Name RVConfRoot -Value (Split-Path $MyInvocation.MyCommand.Path -Parent) -Option Constant 

#-- 証明書インポート/HTTPS有効 start --#
# 証明書のパス
Set-Variable -Name CertPath -Value ("{0}\Certificate\certificate.pfx" -f $RVConfRoot) -Option Constant 
# 証明書のパスワード
Set-Variable -Name CertPass -Value "1qwertyuiop@" -Option Constant 
# HTTPS通信の対象サイト
Set-Variable -Name TargetSite -Value "Default Web Site" -Option Constant 
#-- 証明書インストール/HTTPS有効 end --#

#-- 脆弱性のある暗号化方式対策 start --#
# Protocols
Set-Variable -Name SSL2Server -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server' -Option Constant 
Set-Variable -Name SSL2Client -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client' -Option Constant 
Set-Variable -Name SSL3Server -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server' -Option Constant 
Set-Variable -Name SSL3Client -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client' -Option Constant 
# Ciphers
Set-Variable -Name CiphersNULL -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\NULL' -Option Constant 
Set-Variable -Name DES -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\DES 56\56' -Option Constant 
Set-Variable -Name RC2_40 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 40/128' -Option Constant 
Set-Variable -Name RC2_56 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 56/128' -Option Constant 
Set-Variable -Name RC2_128 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 128/128' -Option Constant 
Set-Variable -Name RC4_40 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 40/128' -Option Constant 
Set-Variable -Name RC4_56 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 56/128' -Option Constant 
Set-Variable -Name RC4_128 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 128/128' -Option Constant 
Set-Variable -Name RC4_64 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 64/128' -Option Constant 
Set-Variable -Name 3DES -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\Triple DES 168' -Option Constant 
# Hashes
Set-Variable -Name MD5 -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes\MD5' -Option Constant 
# KeyExchangeAlgorithms
Set-Variable -Name DiffieHellman -Value 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\Diffie-Hellman' -Option Constant 
#-- 脆弱性のある暗号化方式対策 end --#

#-- IISサービス設定 start --#
# IISサービス名
Set-Variable -Name IISSvcName -Value 'W3SVC' -Option Constant 
#-- IISサービス設定 end --#

#-- Web.config配置設定 start --#
# セキュリティ上推奨されるHTTPヘッダーを追加するWeb.configのディレクトリ
Set-Variable -Name WebConfigSrc -Value ('{0}\Config' -f $RVConfRoot) -Option Constant 
# セキュリティ上推奨されるHTTPヘッダーを追加するWeb.config
Set-Variable -Name WebConfigFile -Value 'web.config' -Option Constant 
# IISのルートディレクトリ
Set-Variable -Name IISRoot -Value 'C:\inetpub\wwwroot' -Option Constant 
#-- Web.config配置設定 end --#

#-- 削除するIISデフォルトアイテム start --#
# IISのデフォルトアイテム一覧
$IISDefaultItems = @(('{0}\iis-85.png' -f $IISRoot), ('{0}\iisstart.htm' -f $IISRoot))
Set-Variable IISDefaultItems -Option ReadOnly
#-- 削除するIISデフォルトアイテム end --#

#-- Web.config配置設定 start --#
# urlscanのインストーラー
Set-Variable -Name UrlScanInstaller -Value ('{0}\Installer\urlscan_v31_x64.msi' -f $RVConfRoot) -Option Constant 
# UrlScan設定ファイルのディレクトリ
Set-Variable -Name UrlScanConfigSrc -Value ('{0}\Config' -f $RVConfRoot) -Option Constant 
# UrlScan設定ファイルの配置先
Set-Variable -Name UrlScanConfigDst -Value 'C:\Windows\System32\inetsrv\urlscan' -Option Constant 
# UrlScan設定ファイル
Set-Variable -Name UrlScanConfigFile -Value 'UrlScan.ini' -Option Constant 
#-- Web.config配置設定 end --#

あとは関数たち.

Deploy-WebConfig.ps1ではIISのルートディレクトリにセキュリティ診断対策を施した設定ファイルを置くだけ.

Deploy-WebConfig.ps1

function Deploy-WebConfig() {
    <#
    .SYNOPSIS
    Web.configを配備します.
    .DESCRIPTION
    IISのルートディレクトリにセキュリティ上推奨されるHTTPヘッダーを付加するようになります.
    規定のドキュメントを無効にします.
    #>
    Param(
        $WebConfigSrc, 
        $IISRoot, 
        $WebConfigFile
    )
    Robocopy.exe $WebConfigSrc $IISRoot $WebConfigFile
}

Disable-Encryption.ps1ではレジストリを弄って脆弱性のある暗号化方式を無効にする.

Disable-Encryption.ps1

function Disable-Encryption() {
    <#
    .SYNOPSIS
    指定したキーの暗号化を無効化します.
    #>
    Param(
        $Key
    )

    New-Item $Key -Force
    New-ItemProperty -path $Key -name Enabled -value 0 –PropertyType DWORD
    if($Key.Contains("SSL")){
        # SSLはDisabledByDefaultも設定する
        New-ItemProperty -path $Key -name DisabledByDefault -value 1 –PropertyType DWORD
    }
}

Enable-SSL.ps1ではSSL証明書をインポートしてHTTPSの443にバインドする.

Enable-SSL.ps1

function Enable-SSL(){
    <#
    .SYNOPSIS
    SSL証明書をインポートし, https通信を有効にします.
    #>
    Param(
        $CertPath,
        $CertPass,
        $TargetSite
    )
      
    # 証明書をインポートする  
    $cert = Import-PfxCertificate -Filepath $CertPath -Password (ConvertTo-SecureString $CertPass -AsPlainText -Force) -CertStoreLocation "Cert:\LocalMachine\My" -Exportable
    # IISに443httpsバインドを作成する
    New-WebBinding -Name $TargetSite -IP "*" -Port 443 -Protocol https
    # httpsバインドを取得し証明書を設定する
    $httpsBinding = Get-WebBinding -Protocol https
    $httpsBinding.AddSslCertificate($cert.GetCertHashString(),"my")
}

Install-UrlScan.ps1ではUrlScanをインストールして, 設定ファイルを置き換える.

Install-UrlScan.ps1

function Install-UrlScan() {
    <#
    .SYNOPSIS
    urlscanをインストールしセキュリティ対策を施した設定ファイルに置き換えます.
    #>
    Param(
        $UrlScanInstaller,
        $UrlScanConfigSrc, 
        $UrlScanConfigDst, 
        $UrlScanConfigFile
    )

    $process = Start-Process -FilePath "msiexec.exe" -ArgumentList ("/i {0} /passive" -f $UrlScanInstaller) -Verb runas -PassThru
    Wait-Process -Id ($process.Id)
    Rename-Item -Path ("{0}\{1}" -f $UrlScanConfigDst, $UrlScanConfigFile) -NewName ("_{0}" -f $UrlScanConfigFile) -Force
    Robocopy.exe $UrlScanConfigSrc $UrlScanConfigDst $UrlScanConfigFile
}

Remove-IISDefaultItems.ps1ではIISをインストールした際にデフォルトで置かれているアイテムを削除する.

Remove-IISDefaultItems.ps1

function Remove-IISDefaultItems() {
    <#
    .SYNOPSIS
    IISのデフォルトページや画像を削除します.
    #>
    Param(
        $Items
    )

    foreach($item in $Items) {
        if(Test-Path $item){
            Remove-Item -Path $item -Force
        }
    }
}

Restart-IIS.ps1ではIISを再起動する. わざわざ関数にしなくても

Restart-IIS.ps1

function Restart-IIS() {
    <#
    .SYNOPSIS
    IISを再起動します.
    #>
    Param(
        $SvcName
    )

    Restart-Service $SvcName
}

Web.configの中身

  • defaultDocumentで規定のドキュメントを無効にする(本番環境にデフォルトのページはいらない).
  • directoryBrowseでディレクトリリスティング(ブラウザにディレクトリ構造見せちゃうヤツ)を無効にする.
  • customHeadersで推奨されるHTTPヘッダーを追加する.
    • X-Content-Type-Options: nosniff(これ指定しないとIEがレスポンス解析してContent-Type無視した動作しちゃったり...)
    • X-Frame-Options: DENY(これ指定しないとクリックジャッキングされる恐れ有り)
    • X-XSS-Protection: 1; mode=block(ブラウザのXSSフィルターの機能を有効にし, XSSを検知したらページのレンダリングを停止する)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.web>
        <httpRuntime enableVersionHeader="false" />
    </system.web>
    <system.webServer>
        <defaultDocument enabled="false" />
        <directoryBrowse enabled="false" />
        <httpProtocol>
          <customHeaders>
            <clear/>
            <remove name="X-Powered-By"/>
            <add name="X-XSS-Protection" value="1; mode=block"/>
            <add name="X-Frame-Options" value="DENY"/>
            <add name="X-Content-Type-Options" value="nosniff"/>
          </customHeaders>
        </httpProtocol>
    </system.webServer>
</configuration>

UrlScan.iniの中身

  • OPTIONSを明示的に無効にする(Webサーバーが許可しているメソッドが表示されちゃうからね).
[AllowVerbs]

;
; The verbs (aka HTTP methods) listed here are those commonly
; processed by a typical IIS server.
;
; Note that these entries are effective if "UseAllowVerbs=1"
; is set in the [Options] section above.
;

GET
HEAD
POST

[DenyVerbs]

;
; The verbs (aka HTTP methods) listed here are used for publishing
; content to an IIS server via WebDAV.
;
; Note that these entries are effective if "UseAllowVerbs=0"
; is set in the [Options] section above.
;

PROPFIND
PROPPATCH
MKCOL
DELETE
COPY
MOVE
LOCK
UNLOCK
SEARCH
PUT
OPTIONS

1リクエストで大量のファイルをアップロード出来ない問題を解決するのが大変だった話

というのもこれに2日もはまった.

経緯

お客様から大量のファイルをアップロードを一括でアップロードできないという問い合わせが来た. これがことのはじまり.

システムの概要

アプリはASP.NET MVCでWebサーバーはIIS.

調査その1

Web.configをいじる

とりあえず「http post limit iis」でぐぐる. 普通はWeb.configをいじればイケルらしい.

maxAllowedContentLengthでHTTPリクエストのContent-Lengthを制限出来るらしい. maxAllowedContentLengthをbyteで指定する. 1 GBまで対応したいので1*1024*1024*1024=1073741824とする.

<configuration>
    <system.webServer>
        <security>
          <requestFiltering>
            <requestLimits maxAllowedContentLength="1073741824" />
          </requestFiltering>
        </security>
    <system.webServer>
</configuration>    

maxRequestLengthでASP.NETにおいてアップロードできるファイルのサイズを制限出来るらしい. maxRequestLengthをKB単位で指定する. こっちもbyteにしてくれ 1 GBまで対応したいので1*1024*1024=1048576とする.

<configuration>
    <system.web>
        <httpRuntime maxRequestLength="1048576" />
    </system.web>
<configuration>  

Web.configをいじってファイルをアップロードしてみる. 結果駄目だった.

問題の原因を探る

Web.configの設定が効いてるのか分からなかったので色々条件を変えてアップロードしてみた.

30 MBに制限してみる

1 GBとたいそれたことは言わないで30 MBに制限して試してみた. すると30 MBまでならファイルをアップロードできたけど, それを超える場合はアップロード出来なかった. どうやらWeb.configは効いているようだ.

もう一回1 GBに制限してみる

1 GBに制限して色々な条件でファイルをアップロードしてみる.

条件 結果
200 MB X
100 MB X
90 MB O
30 MB O

どうやら100 MB未満ならアップロード出来るようだ.

考察

アプリケーションにリクエストが届く前になにかフィルターかかってる? でもよくわからん.

調査その2

エラーの詳細を見るために色々設定した.

トレースを有効にする

IISにトレース機能が追加されていない場合はサーバーマネージャーでIISにトレースを追加する. IISマネージャで対象のサイトを選択した後, 失敗した要求のトレースをクリックし, トレースを有効にする.

トレース規則を設定する

IISマネージャで対象のサイトを選択し, 機能ビューの失敗した要求のトレースの規則をダブルクリックする. 追加→すべてのコンテンツ→状態コード(今回は403, 404)→プロバイダーを全て選択で規則を設定する.

エラーを発生させる

大量のファイルをアップロードする. エラーが「C:\inetpub\logs\FailedReqLogFiles\W3SVC1」にxmlではかれる.

エラーの内容を見る

見てみると気になる内容が...

FILTER_START FilterName="C:\Windows\system32\inetsrv\urlscan\urlscan.dll"

FILTER_SET_REQ_HEADER HeaderName="URLSCAN-STATUS-HEADER:", HeaderValue="Content-length-too-long"

GENERAL_REQUEST_HEADERS Headers="Cache-Control: no-cache

Connection: close
Content-Length: 0
Content-Type: multipart/form-data; boundary=---------------------------7e21c9181f09b6
Accept: text/html, application/xhtml+xml, */*
Accept-Encoding: gzip, deflate
Accept-Language: ja-JP
URLSCAN-STATUS-HEADER: Content-length-too-long

urlscan??? Content-length-too-long???

とりあえずContent-Lengthが長すぎって怒られていることはわかった. なんかurlscanってのが動いてそう.

urlscanについてぐぐる

「urlscan iis」でぐぐった. すると

URLScan v3.1 は、インターネット インフォメーション サービス (IIS) が処理する HTTP 要求の種類を制限するセキュリティ ツールです。

らしい.

urlscanのログを見る

「%WINDIR%\system32\inetsrv\urlscan\logs」にログがあるらしいので見てみることに.

#Software: Microsoft UrlScan 3.1
#Version: 1.0
#Date: 2018-06-07 06:30:42
#Fields: Date Time c-ip s-siteid cs-method cs-uri x-action x-reason x-context cs-data x-control
2018-06-07 06:31:24 10.94.33.151 1 POST /example/fileupload Rejected Content+length+too+long Content-Length: 203675502 100000000

これだ!!!!!!!!! これでurlscanが原因だと確定. urlscanでどう制限しているのかぐぐる.

urlscanの設定をいじる

「urlscan iis content-length」でぐぐった. すると「%WINDIR%\system32\inetsrv\urlscan\」にUrlScan.iniなる設定ファイルがあるそうだ. 見てみた.

[RequestLimits]

;
; The entries in this section impose limits on the length
; of allowed parts of requests reaching the server.
;
; It is possible to impose a limit on the length of the
; value of a specific request header by prepending "Max-" to the
; name of the header.  For example, the following entry would
; impose a limit of 100 bytes to the value of the
; 'Content-Type' header:
;
;   Max-Content-Type=100
;
; Any headers not listed in this section will not be checked for
; length limits.
;
; There are 3 special case limits:
;
;   - MaxAllowedContentLength specifies the maximum allowed
;     numeric value of the Content-Length request header.  For
;     example, setting this to 1000 would cause any request
;     with a content length that exceeds 1000 to be rejected.
;     The default is 30000000.
;
;   - MaxUrl specifies the maximum length of the request URL,
;     not including the query string. The default is 260 (which
;     is equivalent to MAX_PATH).
;
;   - MaxQueryString specifies the maximum length of the query
;     string.  The default is 2048.
;

MaxAllowedContentLength=100000000
MaxUrl=26000
MaxQueryString=2048000

犯人はお前だったんだな... UrlScan.iniのMaxAllowedContentLengthを1073741824に設定したら問題なくファイルがアップロードできたとさ. めでたしめでたし.

まとめ

1リクエストで大量のファイルをアップロードする前にUrlScanに気をつけよう.

参考

Request Limits

httpRuntime

urlscan

PowerShellで設定ファイルの内容をコンソールとファイルに出力するようにしてみた

スクリプトを実行する際に設定ファイルの内容をコンソールで確認出来るようにしたらミスも防げるしいいかなーと思って考えてみた. ついでにtsvで出力出来るようにした.

スクリプト

設定ファイルはこんな感じ. PowerShellでは-Option Constantで定数を宣言できる. 変数の内容は-Descriptionで書く.

Get-Variableで変数の一覧を取得出来るがいらないものまで色々出てきてしまう(気になる人は手元でお試しあれ). そこで-Descriptionの先頭にタグを付けることでユーザー定義の変数かそうでないかを判別するようにした.

あとはFormat-Tableでコンソールに表示して, Export-Csvでファイル出力すれば出来上がり.

Config.ps1

# タグ
Set-Variable -Name UserDefinedVarTag -Value "UserDefinedVars:" -Option Constant
# サンプルフラグ
Set-Variable -Name IsSample -Value $true -Option Constant -Description ("{0}サンプルフラグ" -f $UserDefinedVarTag)
# スクリプトのパス
Set-Variable -Name ConfRoot -Value (Split-Path $MyInvocation.MyCommand.Path -Parent) -Option Constant -Description ("{0}スクリプトのパス" -f $UserDefinedVarTag)
# サンプルディレクトリ
Set-Variable -Name SampleDir -Value ("{0}\Sample" -f $ConfRoot) -Option Constant -Description ("{0}サンプルディレクトリ" -f $UserDefinedVarTag)
# ユーザー名
Set-Variable -Name UserName -Value "TestUser" -Option Constant -Description ("{0}ユーザー名" -f $UserDefinedVarTag)
# ユーザーパスワード
Set-Variable -Name UserPassword -Value "PaSsWoRd" -Option Constant -Description ("{0}パスワード" -f $UserDefinedVarTag)
# 設定ファイルの出力パス
Set-Variable -Name ConfFilePath -Value ("{0}\Config.tsv" -f $ConfRoot) -Option Constant -Description ("{0}設定ファイル内容出力先" -f $UserDefinedVarTag)

function Write-Config(){
    <#
    .SYNOPSIS
    設定内容をコンソールに出力します.
    #>

    $vars = @()
    Get-Variable | ForEach-Object{ if($_.Description -ne $null -and $_.Description.Contains($UserDefinedVarTag)) { $vars += $_ } }
    $vars | ForEach-Object{ $_.Description = $_.Description.Split(":")[1] }
    $vars | Select-Object -Property @{n="変数名"; e={$_.Name}}, @{n="値"; e={$_.Value}}, @{n="詳細"; e={$_.Description}} | Format-Table
    $vars | Select-Object -Property  @{n="変数名"; e={$_.Name}}, @{n="値"; e={$_.Value}}, @{n="詳細"; e={$_.Description}} | Export-Csv -Delimiter "`t" -Encoding default -Path $ConfFilePath -NoTypeInformation
}

設定ファイルを読み込んだスクリプトで関数を呼び出す.

Write-Config.ps1

set-variable -name ScriptRoot -value (Split-Path $MyInvocation.MyCommand.Path -Parent) -option Constant

# 設定ファイル読み込み
. ("{0}\Config.ps1" -f $ScriptRoot)

Write-Config

実行してみるとコンソールに設定内容がきれいに表示される.

PS D:\Sample> .\Write-Config.ps1

変数名       値                                             詳細
------       --                                             ----
ConfFilePath D:\Common\PowerShell\Modules\Sample\Config.tsv 設定ファイル内容出力先
ConfRoot     D:\Common\PowerShell\Modules\Sample            スクリプトのパス
IsSample     True                                           サンプルフラグ
SampleDir    D:\Common\PowerShell\Modules\Sample\Sample     サンプルディレクトリ
UserName     TestUser                                       ユーザー名
UserPassword PaSsWoRd                                       パスワード

ファイルにも出力される.

"変数名"  "値"   "詳細"
"ConfFilePath"  "D:\Common\PowerShell\Modules\Sample\Config.tsv"    "設定ファイル内容出力先"
"ConfRoot"  "D:\Common\PowerShell\Modules\Sample"   "スクリプトのパス"
"IsSample"  "True"  "サンプルフラグ"
"SampleDir" "D:\Common\PowerShell\Modules\Sample\Sample"    "サンプルディレクトリ"
"UserName"  "TestUser"  "ユーザー名"
"UserPassword"  "PaSsWoRd"  "パスワード"

http-serverでサクッとローカルにHTTPサーバをたてる

http-serverでサクッとローカルにHTTPサーバをたてる

ローカルにHTTPサーバたてたいなーと思ったので調べてみたら便利なものがあったので紹介します。

http-server

https://github.com/indexzero/http-server

Node.jsで動くHTTPサーバデス。

http-serverをインストールして起動してみる

HTTPサーバをたてたいところでまずはnpm init。

cd D:\Test\sample
npm init -y

続けてhttp-serverをインストールします。
今回はローカルに。

npm install http-server --save-dev

pakcage.jsonを修正します。

  "scripts": {
    "http-server": "http-server"
  }

これでhttp-serverを起動することが出来るようになりました。

あとはHTTPサーバにコンテンツを置いてブラウザから見てみましょう。

D:\Test\sample配下にPublicフォルダを作成します。
あとはPublicフォルダにコンテンツを置きます。

D:Test\Test\sample
├node_modules
├public
│ ├index.html
│ └sample.md
└pakcage.json

index.html

<DOCTYPE html>
<html>
<head>
  <title>サンプル</title>
  <meta charset="utf-8">
  <style>
    body { font-![slide.png](http://luna.science.nttdata-ccs.co.jp:3000/files/5abb2aa9ad52f21d77615203)
family: 'Droid Serif'; }
    h1, h2, h3 {
      font-family: 'Yanone Kaffeesatz';
      font-weight: normal;
    }
    .remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; }
  </style>
</head>
<body>
  <!-- Remarkjs -->
  <script src="https://remarkjs.com/downloads/remark-latest.min.js" type="text/javascript"></script>
  <script type="text/javascript">
    var slideshow = remark.create({sourceUrl: "sample.md"});
  </script>
</body>
</html>

sample.md

name: inverse
layout: true
class: center, middle, inverse
---
# 見れた?

では早速http-serverを起動してみましょう。
port番号は適当に10000を指定してます。

npm run http-server -- -p 10000

http://localhost:10000 にアクセスしてみます。

f:id:mt9116:20180328144258p:plain

表示されましたー
たったこれだけでHTTPサーバが使えるなんて便利ですよね。