2025年6月9日月曜日

Gradle 8.10.0 への苦難の記録(1)

Android の Project を Build しなくてはならなくなりました。気が重たい。Gradle のバージョンが上がると、必ずビルドが通らなくなります。今回も鬼のように問題が噴出して対応に1週間以上の時間を取られました。過去も大抵、数日から1週間以上の時間を要しました。

 なぜ、こんなに時間を取られるかというと、gradle の api が高速に移動するのでエンドユーザがバージョン毎にapiの書き換えや対応を強要される事、gradle 自身が変更により毎回バグを混入させる事にあります。 例を挙げれば、targetSdkVersion, compileSdkVersion は deprecate ですなんてメッセージが出て、信用して build.gradle の targetSdkVersion, compileSdkVersion の行を削除すると、Sync Gradle では、targetSdkVersion だか compileSdkVersion だかが見つからないとメッセージを表示して Sync に失敗するという体たらくです。 思いつきで、何でも簡単に変更しすぎです。gradle 自身も自身の下した api や property の廃棄や変更についていけてないし、バージョン間の整合も取れないので、ビルドシステム全体として見た場合は、ぐちゃぐちゃでカオスな状況が生じています。

 あなたが、Windows のアプリを開発していて、ある日 CreateFileEx という API名が GenerateFileEx という名前に変更されました。なんてアナウンスを受けたらどうなるか、想像がつきますか? コンパイルオプション /EHsc が急に廃止されて、config.options というファイルに記述しないと動作しなくなったら、どうなるか想像がつきますか?例外を有効にするオプションがデフォルト true から false に変更されたら、どうなるか想像がつきますか?

gradle では、それが日常的に病的に起こります。数か月前までビルドが通っていたはずなのに、ビルドが壊れて動かなくなるんです。... was moved. ... wad deprecated. ... was deprecated. ... was moved. まぁ、まだ moved や deprecated を表示してくれるようになっただけでも有り難いんですけどね。以前はエラーメッセージを検索して StackOverflowを見て、deprecated だった事を知って対策してましたから。ただ、エラーメッセージを検索してネットの情報を調べないと対処方法がわからないのは、相変わらずです。

  Copilot くんにお願いして名称の変更に限定して、変更をまとめてもらいました。
私の記憶が確かならば、この表は氷山の一角です。taskを使用している場合、taskの依存関係を記述しなければならないのですが、ヒステリックにtask名が変更されて、その都度、プロジェクトの修正を余儀なくされました。ある機能のtask名は、3回以上名前が変更されました。debugは間違ってるreleaseだ、いやJarじゃないRFileだといった具合に…。ころころと名前変えられたら、プロジェクトファイルも全部書き換えないと動きません。ファイルをコピーする関数名も変更されましたし、gradleの変更頻度は病的で、まともではありません。担当者のお気持ちで担当者がその名前が気に入らなければ名前が変更されます。エンドユーザは、気まぐれで変更された名前に合わせるようプロジェクトを修正しなければなりません。毎回毎回、山のように修正を余儀なくされます。


イライラはピークに達するものの落ち着いて、次のような書き込みをする決断に至ります。
Why does gradle deprecate and remove things so often? に象徴されていると思います。
 話を戻します。

 今回ビルドするプロジェクトは、legacy な Java による Application の話であり、KotlinでもGroovyでもありません。AndroidStudioにより作成できるプロジェクトは、Kotlin と Groovy の2択でlegacyのプロジェクトという選択肢が無いのも頭の痛い問題です。KotlinでもGroovyでもないのに、プロジェクトのバージョンアップ中に kotlin や groovy といったエラーを見かけ、これらのエラーを見る度に一体何のエラーなのか意味不明で頭の痛い思いをしました。

ライブラリのビルド
 gradle-3.5-all.zip と gradle-3.5.2
 gradle-7.5-all.zip と gradle-7.4.2 
以上の組み合わせから 
 gradle-8.14.1-bin.zip と gradle-10.0.0
へと移行する形になりました。ファイル名も -all から -bin に変更されています。なんでも allはファイルサイズがデカくなるからなんだそうです。gradle-8.14.0-all.zip ってやってエラーになるから何事や?と思ったら、知らんがな。いちいち、こんな調子で時間を取られます。
ここを参照すればわかるように私も質問者と同じ感覚です。
尚、試行錯誤している間は gradle-8.14.0-bin.zip しか出ていませんでしたが、途中で8.14.1がリリースされて、まともにビルドできるようになりました。その間はライブラリの依存関係バグがあったのか、何をやってもビルドが通りませんでした。

gradle-8.x になって変更された箇所は

・AndroidManifest.xml から package="com.foo.bar" の属性が削除された事
・build.gradle(app) で android { } の 下に namespace = "com.foo.bar" の記述が必要になった事。

ビルド時にサジェストがあったので、これに関しては比較的親切な対応と言えます。

しかし、こんな変更必要ですか?案の定、この変更によって、package.R.class が出力されないバグが混入し、アプリケーションのビルド時に、package R does not exist. というエラーが発生して、apk が作成できませんでしたが、gradle-8.10.1 で修正されました。gradle に振り回されて修正しても直らない地獄を徘徊しているうちに修正されました。

今回も deprecated の嵐で、バージョンアップすると、ことごとくビルドが失敗しました。何箇所も問題が発生し、たくさんの修正が必要でした。ライブラリをまともにビルドできるまでに4日以上費やしました。この他に android API の deprecated 対応にも4日以上の時間を取られました。

まずは、JDKのバージョンの整理が必要でした。今までは、なんとなくビルドできていましたが、そうはいきませんでした。
gradle は JDK のバージョンに煩い。jdk-11 じゃないと動きません。jdk-17 じゃないと動きません。jdk-18じゃないと動きません。と、色々注文が多いです。開発環境では、Oracleがインストールされていると、oracle が javac に対して path を通していたり、Visual Studio が javac に path を通していたり、Android Studio は SDK に設定されている JDKを使用しようと毎回 javac をリセットしたり、gradle は GRADLE 専用の JDK の PATH を設定してたりします。バージョンアップ中に GRADLE_JDK_LOCAL_PATH という名前への変更が行われたので、とにかく専用のPATHが設定されているという事です。これらpathの仕様は担当者の気まぐれで変更される恐れがあります。ライブラリのビルドにはJDK-17、アプリのビルドにはJDK-11を使用しました。プロジェクト>設定>検索>gradle で、モジュール>gradle の JDKの指定と、ウィンドウズの環境変数 path を制御する事で自分は対応しました。これに関しては gradleが JDKxxでは動作しないとメッセージを表示してくれるので対応しやすいです。

いろいろありすぎて、書ききれません…続きます。

android ProgressBar が deprecated

ProgressBar が deprecated になっていた。
ダイアログを表示すると、他の操作ができないから埋込レイアウトにしとけ
というのがGoogle先生の指示なんだけど、アプリケーションのステート的には、このタスクが終了しないと、どのみち次のオペレーションはできないので、埋込にする意味がないと思う。
処理の途中でアプリを切り替えられると、onDestroyが呼ばれるので、それも困る
そんな時はやり直しですしおすし。
それにダイアログを表示していてもアプリは切り替えられる
埋め込んだところで、大差がないと思うのである。
で、いちいち埋込の画面を作成するのは、大変なのでプログレスダイアログは欲しい。
package com.foo.Helper;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import android.widget.ProgressBar;

import com.foo.helper.R;

public class CommonProgressDialog extends DialogFragment {

	private final Context context_;

	private final String title_;

	private final String message_;

	private AlertDialog.Builder builder_;

	private AlertDialog dialog_;

	private ProgressBar progressBar_;

	@NonNull
	@Override
	public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
		dialog_ = builder_.create();
		return dialog_;
	}

	private void BuildBase(final Context context, final String title, final String message) {
		builder_ = new AlertDialog.Builder(context)
				.setView(R.layout.progress)
				.setTitle(title)
				.setCancelable(false)
				//.setCanceledOnTouchOutside(false)
				.setMessage(message);
	}

	public CommonProgressDialog(final Context context, final String title, final String message) {
		this.context_ = context;
		this.title_ = title;
		this.message_ = message;
		BuildBase(context, title, message);
	}

	/// 中止ボタンを設置する
	public void setNegative(final DialogInterface.OnClickListener l) {
		if( dialog_ == null ) return;
		dialog_.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), l);
	}

	public void setMax(int maxValue) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setMax(maxValue);
		}
	}

	public void setProgress(int progress) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setProgress(progress);
		}
	}


	public void show() {
		if( dialog_ == null) {
			dialog_ = builder_.create();
		}
		dialog_.show();
	}

	public void dismiss() {
		dialog_.dismiss();
		dialog_ = null;
	}

	/*
	public CommonProgressDialog(Context context) {
		super(context);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}
	public CommonProgressDialog(Context context, int theme) {
		super(context,theme);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}

	 */
}


progress.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="20dp">
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:gravity="center"
        android:text="Please wait! This may take a moment." />
</LinearLayout>

2024年1月5日金曜日

WPF ComboBoxにおけるIMEの制御

いやー、WPFのTextBoxならIMEの制御を設定できるんですけど、ComboBox のEditableをFalseのままで、IsTextSearchEnabled="True"とか設定して、項目を[コード]:[名前]にした時にIMEをオフにしないと、コード入力が効かないんですわ。
いろいろ検索してみたんですが、まともなのが無かったんで、書きましたよ。はい。
public class Util
{

    [DllImport("imm32.dll")]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmReleaseContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetConversionStatus(IntPtr hIMC, Int32 fdwConversion, Int32 fdwSentence);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetOpenStatus(IntPtr hIMC, Int32 fOpen);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmGetOpenStatus(IntPtr hIMC);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmAssociateContext(IntPtr hWnd, Int32 hIMC);

    const Int32 IME_CMODE_ALPHANUMERIC = 0x0000;
    const Int32 IME_CMODE_NATIVE = 0x0001;
    const Int32 IME_CMODE_CHINESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_HANGUL = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_JAPANESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_KATAKANA = 0x0002;  // only effect under IME_CMODE_NATIVE
    const Int32 IME_CMODE_LANGUAGE = 0x0003;
    const Int32 IME_CMODE_FULLSHAPE = 0x0008;
    const Int32 IME_CMODE_ROMAN = 0x0010;
    const Int32 IME_CMODE_CHARCODE = 0x0020;
    const Int32 IME_CMODE_HANJACONVERT = 0x0040;
    const Int32 IME_CMODE_NATIVESYMBOL = 0x0080;

    const Int32 IME_CMODE_SOFTKBD = 0x0080;
    const Int32 IME_CMODE_NOCONVERSION = 0x0100;
    const Int32 IME_CMODE_EUDC = 0x0200;
    const Int32 IME_CMODE_SYMBOL = 0x0400;
    const Int32 IME_CMODE_FIXED = 0x0800;


    const Int32 IME_CONFIG_GENERAL = 1;
    const Int32 IME_CONFIG_REGISTERWORD = 2;
    const Int32 IME_CONFIG_SELECTDICTIONARY = 3;

    const Int32 IME_SMODE_NONE = 0x0; // センテンスに関する情報はなし
    const Int32 IME_SMODE_PLURALCLAUSE = 0x01; // 変換のための複文情報を使う
    const Int32 IME_SMODE_SINGLECONVERT = 0x02; // 単漢字変換する
    const Int32 IME_SMODE_AUTOMATIC = 0x04; // 自動モードで変換
    const Int32 IME_SMODE_PHRASEPREDICT = 0x08; // 次の文字を予想するためにフレーズ情報を使う
    const Int32 IME_SMODE_CONVERSATION = 0x0010;

    /// <summary>
    /// IMEモード
    /// </summary>
    public enum ImeMode
    {
        Off = 1, // IME オフ
        Hiragana = 2, // 全角ひらがな
        Katakana = 3, // 全角カタカナ
        HalfKatakana = 4, // 半角カタカナ
        Alpha = 5, // 全角英数
        HalfAlpha = 6, // 半角英数
    };


    /// <summary>
    /// IMEを制御する。WPF ComboBox 等の一部コントロールでは、IMEの制御ができない。そのための対応。
    /// TextBox等では、下記で制御できる
    /// <TextBox InputMethod.IsInputMethodEnabled="False"/> IME無効
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="FullShape,Native"/> ひらがな
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,FullShape"/> カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,Native"/> 半角カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric,FullShape"/> 全角英数
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric"/> 半角英数
    /// </summary>
    /// <param name="ctrl"></param>
    /// <param name="mode"></param>
    public static void SetImeMode(Control ctrl, ImeMode mode)
    {
        IntPtr handle = IntPtr.Zero;
        HwndSource hwnd = PresentationSource.FromVisual(ctrl) as HwndSource;
        if (hwnd != null)
        {
            handle = hwnd.Handle;
        }
        IntPtr himc = ImmGetContext(handle);
        Int32 dwConversion = 0;

        try
        {
            if( mode == ImeMode.Off)
            {
                if (ImmGetOpenStatus(himc) != 0)
                {
                    ImmSetOpenStatus(himc, 0);
                }
                return;
            } else
            {
                if( ImmGetOpenStatus(himc) == 0)
                {
                    ImmSetOpenStatus(himc, 1);
                }
            }
            switch (mode)
            {
                case ImeMode.Hiragana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_JAPANESE, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Katakana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfKatakana:
                    ImmSetConversionStatus(himc, IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Alpha:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfAlpha:
                    ImmSetConversionStatus(himc, IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
            }
        }
        finally
        {
            ImmReleaseContext(handle);
        }

    }

}
使い方は、GotFocusイベントをハンドリングして
   private void FooComboBox_GotFocus(object sender, RoutedEventArgs e)
   {
       Util.SetImeMode(sender as ComboBox, Util.ImeMode.Off);
   }
という感じ

2023年9月22日金曜日

コマンドプロンプトとCURLのエスケープ

コマンドプロンプトのエスケープは、\ と " がエスケープ処理の対象となります。

CURL のエスケープは、\ { } | がエスケープ処理の対象となります。

\ をエスケープすると、コマンドプロンプト用のエスケープとCURL用のエスケープが複合し、\\\\という形になります。

ふむふむ…
ふむふむ…
ふむふむふむふむ?…

コマンドプロンプトは \\\\ を " として取り扱います…

(`□′)╯┴┴

2023年6月23日金曜日

atldbcli.h のバグを踏んだ

atldbcli.h の CCommand<CDynamicStringAccessor> を利用していたコードが null pointer exception で落ちるようになった。
ほぇ?(#^ω^)ピキピキ

デバッガでトレースしていくと、m_pAccessor のメンバが nullptr で初期化されないまま放置されていた。
で、Accessor クラスは、どこに行った?
おそらく、class template refactoring により、自分自身に継承する形になったようです。

なので、CCommand<CDynamicStringAccessor>::SetAccessor 関数を使用して自分自身を設定する必要がありました。
せっかくなので、使い方のサンプル込みで、紹介です。
#include <iostream>
#include <objbase.h>
#include <atldbcli.h>

int main(int argc, char* argv[] )
{
  HRESULT hr = CoInitialize(NULL );
  if(FAILED(hr)) {
    std::cerr << "Fail to Initialize COM:" << hr << std::endl;
  }

  CDataSource ds;

  // OLEDBによるODBC接続文字列
  hr = ds.OpenFromInitializationString(
     L"Provider=MSDASQL.1;Password=password;"
     L"Persist Security Info=True;"
     L"User ID=username;"
     L"Data Source=odbc_data_source_name;"
     L"Extended Properties=\"DSN=data_source_name;UID=username;PWD=password\""
  );

  CSession ss;

  CCommand<CDynamicStringAccessor> rs;

  hr = ss.Open(ds );
  hr = rs.Open(ss, "select * from hoge" );

  // CCommand<typename T>::m_pAccessor を初期化するコードが欠落しているため
  // 自分自身を設定する必要がある(どこかのタイミングで混入したバグ対応)
  // 2023/7/13 修正されたような気がしますが、下記コードは正しくなくなった感じがします。
  // 仕様がよくわかりません。
  rs.SetAccessor(&rs);

  hr = rs.MoveNext();
  if( FAILED(hr) || hr == DB_S_ENDOFROWSET ) {
    std::cerr << "Fail to MoveNext: " << hr << std::endl;
  }
  // Get the column information
  ULONG ulColumns = rs.GetColumnCount();
  
  DBTYPE dbtype;
  rs.GetColumnType( 1, &dbtype );
  dbtype &= 0x3FFF;	// DBTYPE_BYREF を除去	
  if( 
       dbtype == DBTYPE_I4
    || dbtype == DBTYPE_UI4
    || dbtype == DBTYPE_R8
    || dbtype == DBTYPE_STR
    || dbtype == DBTYPE_WSTR
    || dbtype == DBTYPE_DECIMAL
    || dbtype == DBTYPE_VARNUMERIC
    || dbtype == DBTYPE_NUMERIC
  ) {
    // よくあるフィールド型
  } else {
    // 対応可能か検討が必要なフィールド型
    std::cerr << "Un supported dbtype: " << dbtype << std::endl;
  }

  int nMax = 10;
  while(nMax) {
    std::string strData;
    for( DBORDINAL col = 1; col <= ulColumns; col++ ) {
      DBSTATUS dStatus;
      DBLENGTH nLength;
      rs.GetStatus( col, &dStatus );
      rs.GetLength( col, &nLength );
      if( col > 1 ) {
        std::cout << ", ";
      }
      if( FAILED( dStatus ) ) {
        std::cout << "<<ERROR>>";
      } else if( dStatus == DBSTATUS_S_ISNULL ) {
        std::cout << "<<NULL>>";
      } else {
        std::cout << rs.GetString(col);
      }
    }
    std::cout << std::endl;
    hr = rs.MoveNext();
    if( FAILED(hr) || hr == DB_S_ENDOFROWSET )  break;
    --nMax;
  }

  rs.Close();
  ss.Close();
  ds.Close();
  CoUninitialize();

  return 0;
}
2023/07/13 追記: バグは修正されたのか、仕様が変わったのか、よくわからない状況になりました。

2022年9月22日木曜日

C#の Properties.Settings.Default が実際にどこに保存されるか忘備録

C# の Properties.Settings.Default 言語機構として用意していただけるのは有難いのですが、ここに環境系の設定を書いておくとインストールされたマシンの環境によって設定を変えたいとか出てくるんですよ。
ところが、こいつ、一体どこのファイルの設定を見てるねん???(#^ω^)ピキピキ
ってキレそうになる仕様なわけです。

という事で、.Net の古いバージョンでは、下記コードで導き出される場所に初期値が書かれています。
   System.Reflection.Assembly asm =System.Reflection.Assembly.GetExecutingAssembly();
   textBox1.Text = asm.GetName().Version.ToString(); // バージョン番号を取り出す
   // config のデフォルト値が記述されたファイルの場所
   textBox2.Text =  ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
.Net の仕様が変わり、現在は
   // Foo.exe.config 場合によっては Foo.dll.config 
   // ここは作成したプロジェクトの実行物の名前に依存します。
   textBox2.Text =  Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Foo.exe.config";
はてさて、ではコードで、これらの要素を変更した値はどこに書き込まれるのか?
いつも大変お世話になります。Stackoverflow さまによると
Where are the Properties.Settings.Default stored? によると
c:\users\USER\AppData\Local\COMPANY\APPLICATION.exe_Url_LOOKSLIKESOMEKINDOFHASH\VERSION\user.config
なんだそうで、How know LOOKSLIKESOMEKINDOFHASH? とは、ごもっともな質問。私も知りたい。
これも Stackoverflow にありました。
How to get hash value in user.config path?
(ノ`□´)ノ⌒┻━┻

2022年8月24日水曜日

IISのResponse Header を無効化 忘備録

PowerShell で、IIS の Server ヘッダや、X-ASPNET-Version ヘッダを消去、TRACEメソッドの無効化がしたくて、いろいろ試してみたが、やれ、静的コンテンツではなく、Exe経由のコンテンツのヘッダが消えないとか、ひどい目にあったので忘備録。

IIS ReWrite モジュールが必要です。

管理者権限で、下記スクリプトを実行します。
スクリプトだけでは完結しないので、コメントも読んでください
#
# PowerShell は BOM付き UTF-8 のエンコーディングで保存する事
#  さもなくば、「文字列に終端記号" がありません」という
#  エラーに悩まされます。(BOMの仕様必要?)
#
#
# X-ASPNET-VERSION を無効にする送信書き換えルールを
#   指定された PSPath に追加する
#
function global:AddXASPNetVersionRule {
  param ( $PSPath )

  echo "$PSPath の X-ASPNET-VERSION ヘッダを無効にします"

  $chk = Get-WebConfigurationProperty `
    -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']" `
    -PSPath $PSPath `
    -Name name 

  if( $chk -eq $null ) {
    Add-WebConfigurationProperty `
      -Filter "//rewrite/outboundRules" `
      -PSPath $PSPath `
      -Name "Collection" `
      -Value @{name="REMOVE_X-ASPNET-VERSION"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']/match" `
      -PSPath $PSPath `
      -Value @{serverVariable="RESPONSE_X-ASPNET-VERSION";pattern=".+"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']/action" `
      -PSPath $PSPath `
      -Value @{type="Rewrite"}
    echo "  ...処理完了..."
  } else {
    echo "  ...処理済み skip..."
  }
}

#
# SERVER-VERSION を無効にする送信書き換えルールを
#   指定された PSPath に追加する
#
function global:AddServerVersionRule {
  param ( $PSPath )

  echo "$PSPath の Server ヘッダを無効にします"

  $chk = Get-WebConfigurationProperty `
    -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']" `
    -PSPath $PSPath `
    -Name name

  if( $chk -eq $null ) {
    Add-WebConfigurationProperty `
      -Filter "//rewrite/outboundRules" `
      -PSPath $PSPath `
      -Name "Collection" `
      -Value @{name="REMOVE_SERVER-VERSION"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']/match" `
      -PSPath $PSPath `
      -Value @{serverVariable="RESPONSE_SERVER";pattern=".+"}
    
    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']/action" `
      -PSPath $PSPath `
      -Value @{type="Rewrite"}
    echo "  ...処理完了..."
  } else {
    echo "  ...処理済み skip..."
  }
}

#
# サーバ変数 RESPONSE_X-ASPNET-VERSION を追加する
#     X-ASPNET-VERSION: ヘッダとして利用される変数
#
$asv = Get-WebConfigurationProperty `
       -filter "//rewrite/allowedServerVariables/add[@name='RESPONSE_X-ASPNET-VERSION']"`
       -name name

if ($asv -eq $null)
{
  echo "サーバ変数 RESPONSE_X-ASPNET-VERSION を追加します"
  Add-WebConfigurationProperty `
    -Filter "//rewrite/allowedServerVariables" `
    -PSPath "IIS:\" `
    -Name "Collection" `
    -Value @{name="RESPONSE_X-ASPNET-VERSION"}

}

#
# サーバ変数 RESPONSE_SERVER を追加する
#      SERVER: ヘッダとして利用される変数
#
$asv2 = Get-WebConfigurationProperty `
       -filter "//rewrite/allowedServerVariables/add[@name='RESPONSE_SERVER']"`
       -name name

if ($asv2 -eq $null)
{
  echo "サーバ変数 RESPONSE_SERVER を追加します"

  Add-WebConfigurationProperty `
    -Filter "//rewrite/allowedServerVariables" `
    -PSPath "IIS:\" `
    -Name "Collection" `
    -Value @{name="RESPONSE_SERVER"}
}

#
# X-ASPNET-VERSION ヘッダを無効にする
#

  AddXASPNetVersionRule( "IIS:\" )

  AddXASPNetVersionRule( "IIS:\sites\Default Web Site" )

  # IIS-Admin上で手動追加するとエラーにならないが、コードで
  # 実行するとエラーになる不思議仕様
  #AddXASPNetVersionRule( "IIS:\sites\Default Web Site\foo" )

  # Application で無効化するために必要?
  # 仕様が不明すぎて草
  AddXASPNetVersionRule( "MACHINE/WEBROOT/APPHOST" )

#
# SERVER-VERSION ヘッダを無効にする
#
  AddServerVersionRule( "IIS:\" )

  AddServerVersionRule( "IIS:\sites\Default Web Site" )

  # IIS-Admin上で手動追加するとエラーにならないが、コードで
  # 実行するとエラーになる不思議仕様
  #AddServerVersionRule( "IIS:\sites\Default Web Site\foo" )

  # Application で無効化するために必要?
  # 仕様が不明すぎて草
  AddServerVersionRule( "MACHINE/WEBROOT/APPHOST" )


#
# IISの Server Header を無効化します。
#   静的コンテンツだけに効果があるため気休め
#
  echo "  IIS のサーバヘッダを無効化します  "
  echo "   ※ Application に対しては効果がありません"
  Set-WebConfigurationProperty `
    -pspath 'MACHINE/WEBROOT/APPHOST' `
    -filter "system.webServer/security/requestFiltering" `
    -name "removeServerHeader" `
    -value "True"


#
# IISの TRACE を無効化します。
#   静的コンテンツだけに効果があるため気休め
#
  echo "  IIS のTRACE メソッドを無効化します  "
  echo "   ※ Application に対しては効果がありません"
  echo "   コマンドで対応できる範囲は静的コンテンツのみです"
  echo "   要求フィルター>HTTP動詞>Trace;false をIIS設定から手動で行う必要があります"
#
#  要求フィルター HTTP動詞 TRACE をfalseにする
#
# エラーで動作しなくなるので、コマンドによる、このやり方はダメ
#  Add-WebConfigurationProperty `
#    -pspath "IIS:\" `
#    -filter "/system.webServer/security/requestFiltering/verbs" `
#    -name '.' -value @{verb='TRACE';allowed='False'}
#
  $colls = Get-IISConfigSection -CommitPath 'Default Web Site' -SectionPath 'system.webServer/security/requestFiltering' 
    #echo "colls : $colls"
  $verbs = Get-IISConfigCollection -ConfigElement $colls -CollectionName 'verbs'
    #echo "verbs : $verbs"
  try {
    New-IISConfigCollectionElement -ConfigCollection $verbs -ConfigAttribute @{ 'verb'='TRACE';'allowed'=$false }
    Stop-IISCommitDelay
  } catch {
    # 取得して、値を設定するコードが動作しない。(関数の仕様がわからない)
    #  echo " set trace "
    #  $trace = Get-IISConfigCollectionElement -ConfigCollection $verbs -ConfigAttribute @{ 'verb'='TRACE' }
    #  echo "trace : $trace.allowed $trace.verb"
    #  Set-IISConfigAttributeValue -ConfigElement $trace -AttributeName 'allowed' -AttributeValue $false
  }

#
# Web設定を無効にしてもアプリケーションからのヘッダが無効になるとは言ってない
# 気休め(無い方がまし)
#
#$rule3 = Get-WebConfigurationProperty `
#          -filter "/system.webServer/httpProtocol/customHeaders/add[@name='X-Powerd-By']" `
#          -Name name
#
#if ($rule2 -eq $null)
#{
#  echo "clear X-Powered-By"
#  Clear-WebConfiguration "/system.webServer/httpProtocol/customHeaders/add[@name='X-Powered-By']"
#}