Windows7でWindows PowerShellからタスクを追加するには

Windows7でWindows PowerShellからタスクを追加するにはschtasks.exeを呼び出す以外にないらしい。

Windows PowerShellでタスクの設定を追加するにあたって、Scheduled Tasks Cmdletsを使いたかったのだけど、現状ではWindwos 8.1とWindows 2012 R2以降でしかサポートしません。

昔はWindows7でも後からインストールできた用で、PowerShell V2.0の場合はPowerShell Packをインストールすることで、PowerShell V3.0であれば標準でタスクスケジューラの制御の為のScheduledTask Cmdletを提供していたようです。現在ではPowerShell Packもダウンロード出来ません。最新のPowerShell V4.0ではWindows8.1以降かWindows 2012R2以降でなければScheduled Tasks Cmdletsをサポートしないので仕方ありませんね

コマンドプロンプトからWindowsUpdateを適用して再起動するには

WindowsUpdateを適用して再起動するコマンドは用意されていないが、InitiateShutdown APIを使用すればWindowsUpdateを適用して再起動するアプリケーションを作ることは容易に出来る。サーバーの運用では指定の時間にWindows Updateを適用して再起動する様な処理が必要になる事が多いが、このAPIを使用して自作したコマンドをタスクに登録しておけば良い。

以下がサンプルコード。

#include "stdafx.h"
#include <stdlib.h>
#include <Windows.h>
#include <winreg.h>

int wmain()
{
	// 引数文字列の解析
	DWORD	dwShutdownFlags = 0;
	bool	showHelp = false;
	LPTSTR	comment = NULL;
	for (int i = 0; i < __argc; i++)
	{
		if (0 == _tcsicmp(__targv[i], _T("/r")) || 0 == _tcsicmp(__targv[i], _T("-r")))
		{
			dwShutdownFlags |= SHUTDOWN_RESTART;
		}
		if (0 == _tcsicmp(__targv[i], _T("/s")) || 0 == _tcsicmp(__targv[i], _T("-s")))
		{
			dwShutdownFlags |= SHUTDOWN_POWEROFF;
		}
		if (0 == _tcsicmp(__targv[i], _T("/n")) || 0 == _tcsicmp(__targv[i], _T("-n")))
		{
			dwShutdownFlags |= SHUTDOWN_NOREBOOT;
		}
		if (0 == _tcsicmp(__targv[i], _T("/f")) || 0 == _tcsicmp(__targv[i], _T("-f")))
		{
			dwShutdownFlags |= SHUTDOWN_FORCE_OTHERS;
		}
		if (0 == _tcsicmp(__targv[i], _T("/u")) || 0 == _tcsicmp(__targv[i], _T("-u")))
		{
			dwShutdownFlags |= SHUTDOWN_INSTALL_UPDATES;
		}
		if (0 == _tcsicmp(__targv[i], _T("/m")) || 0 == _tcsicmp(__targv[i], _T("-m")))
		{
			i++;
			if (i < __argc)
				comment = __targv[i];
		}
		if (0 == _tcsicmp(__targv[i], _T("/h")) || 0 == _tcsicmp(__targv[i], _T("-h")))
		{
			showHelp = true;
		}
	}

	if (showHelp)
	{
		_tprintf(_T("/r : The computer is shut down and rebooted.\n"));
		_tprintf(_T("/s : The computer is shut down and powered down.\n"));
		_tprintf(_T("/n : The computer is shut down but is not powered down or rebooted.\n"));
		_tprintf(_T("/u : The computer installs any updates before starting the shutdown.\n"));
		_tprintf(_T("/f : All sessions are forcefully logged off.\n"));
	}
	else
	{
		HANDLE				hToken;
		TOKEN_PRIVILEGES	TokenPri;

		// プロセストークンを取得
		if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) == FALSE)
		{
			return GetLastError();
		}

		// シャットダウン権限の LUID を取得
		if (LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &TokenPri.Privileges[0].Luid) == FALSE)
		{
			return GetLastError();
		}

		// シャットダウン権限を与える
		TokenPri.PrivilegeCount = 1;
		TokenPri.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
		AdjustTokenPrivileges(hToken, FALSE, &TokenPri, 0, (PTOKEN_PRIVILEGES)NULL, 0);
		if (GetLastError() != ERROR_SUCCESS)
		{
			return GetLastError();
		}

		// Windows Updateを適用して再起動する
		DWORD errCode = ::InitiateShutdown(NULL, comment, 0, dwShutdownFlags, SHTDN_REASON_MINOR_OTHER);
		_tprintf(_T("InitiateShutdown Result Code = 0x%08X.\n"), errCode);
		return errCode;
	}

	return 0;
}

以下はバイナリ(実行ファイル)のダウンロード
InitiateShutdown_x32
InitiateShutdown_x64

「Windowsの機能の有効化または無効化」を行うには・・・

Windows7以降のOSで、グループポリシーやバッチファイル等から「Windowsの機能の有効化または無効化」を行うには、DISMコマンドを使用します。DISMコマンドはWindows自動インストールキット(Windows AIK)という、Windowsを多数のPCに導入するための支援ツールの一つです。Windowsのインストール支援ツールはOSのメジャーバージョンが変わる度に大きな変更が加えられるのが常ですが、DISMコマンド自体はWindows7以降はWindows 10でも後方互換性は維持されているようです。

現在有効化または無効化されている機能の一覧を得るには以下のコマンドを実行します。

dism  /online /Get-Features

特定の機能を有効化するには以下のコマンドを実行します。

dism /online /Enable-Feature /FeatureName:[機能名]

特定の機能を無効化するには以下のコマンドを実行します。

dism /online /Disable-Feature /FeatureName:[機能名]

以下のように機能名を指定すれば、特定機能について有効化されているか取得できますが、%ERRORLEVEL%の値としては取得出来ないようです。

dism  /online /Get-Features /FeatureName:[機能名]

また依存関係を自動では解決してくれません。機能Bを有効化するために、機能Aが必要となる場合には、以下のように依存関係を意識して順番に有効化する必要があります。

dism /online /Enable-Feature /FeatureName:[機能A]
dism /online /Enable-Feature /FeatureName:[機能B]

PowershellでActiveDirectoryのユーザーを管理する

ActiveDirectory(以下AD)のユーザーは人事異動等にともなって定期的に大きく変更されるので、手作用で更新していると非常に煩雑な事になります。Powershellの活用でADのユーザー管理の省力化を目指したいと思います。

Remote Server Administration Toolsのインストール

PowershellでActive Directoryのユーザー管理の為のコマンドレットを呼び出すためには、Remote Server Administration Tools(以下RSAT)をインストールする必要がある。インストールするRSATはクライアントOS毎に異なるので、対応するインストーラを以下からダウンロードする。
Remote Server Administration Tools (RSAT) for Windows Client and Windows Server (dsforum2wiki)

ADユーザーを更新する

更新する対象がデータベースなら削除して登録することを繰り返しても問題ない場合が多いが、ADユーザーの場合には削除して追加すると言うわけにはいきません。一度削除してしまうと、再登録してもユーザーを一意に識別するために使っているSIDやGUIDが異なる値になってしまい、別のユーザーとして識別されてしまいます。従って面倒でも既存のユーザーの有無を調べ、無ければ新規に作成し、あるなら必要に応じて項目を更新すると言う手続きにならざる得ません。

既存ユーザー有無確認

既存ユーザーの有無を確認するにはGet-ADUserを使う。Get-ADUserは一致するユーザーが存在しない場合には例外を投げるので、$existUserの値は変更されないまま$NULLとなる。

     $existUser = $NULL
     $existUser = Get-ADUser -Identity [ユーザー名]
     if ($existUser -eq $NULL)
     {
          # ユーザーが登録されていない場合の処理
     }
     else
     {
          # ユーザーが登録されている場合の処理
     }

ユーザーの追加

ユーザーを追加するにはNew-ADUserを使用します。ユーザーに関する諸設定を行うためにコマンドラインパラメータが非常に多いです。必要最低限を指定して、残りはSet-ADUserで処理した方が実装が容易かと思います。

     New-ADUser "[ユーザー名]"  -UserPrincipalName "[ユーザー名]@[ドメイン] .local") -DisplayName "[表示名]" -Path"OU=[新部署] , OU=[部門], DC=[ドメイン], DC=local " -Enabled $TRUE

既存ユーザーの一覧を取得

Get-ADUserは指定した条件にマッチするユーザーのリストを返す。フィルタ条件にアスタリスクを指定すると全てのユーザーが得られる。抽出条件の指定方法は多岐に渡るので詳細はオンラインヘルプに委ねる。

    $userAccountList = Get-ADUser -Filter *
     foreach ($userAccount in $userAccountList)
     {
          # 各ユーザーに対する処理
     }

組織単位の移動

組織単位はGet-ADUser で取得したユーザーアカウントのプロパティを参照すれば得られる。組織単位を変更する場合には、Move-ADObjectを使用して移動先の組織単位を設定する。

     if ($userAccount.DistinguishedName -notmatch ".*OU=[旧部署].*")
     {
           Move-ADObject -Identity $userAccount. ObjectGUID -TargetPath "OU=[新部署] , OU=[部門], DC=[ドメイン], DC=local"
     }

グループ設定の変更

ユーザーグループに割り当てるにはaddNoneGroupMemberを、ユーザーグループから削除するにはremoveExistGroupMemberを使う。

     # グループに追加する
     addNoneGroupMember $userAccount "グループ名"

     # グループから削除する
     removeExistGroupMember $userAccount "グループ名"

ユーザーの削除

Remove-ADUserを使えば削除する事が出来るが、誤って削除してしまうと同じSIDやGUIDを持つユーザーを復活させようとすると非常に難しくなる。従って削除するのではなくSet-ADUserでユーザーを無効にした上で、特定の組織単位に移動するに止める方が良い。

     if ($userAccount . Enabled)
     {
          $userAccount .Enabled = $FALSE
          Set-ADUser -Instance $userAccount
     }

     if ($userAccount . DistinguishedName -notmatch ".*OU=削除対象.*" )
     {
           Move-ADObject -Identity $userAccount. ObjectGUID -TargetPath "OU=削除対象 , OU=[部門], DC=[ドメイン], DC=local"
     }

グループポリシーでODBCデータソースを設定する

グループポリシーエディタの基本設定→コントロールパネルの設定→データソースの設定を開き、新たにデータソースの設定を追加します。
odbc_property
データソースのプロパティで操作に置換を選択します。
置換を選択すると既存の設定がない場合には新たに作成、既存の設定がある場合にはそれを削除して新たに作成します。更新の場合には既存の設定がある場合には、これを上書きします。作成の場合には既存の設定がない場合には新たに作成、既存の設定がある場合には何もしません。
続いてデータソース名を入力し、ドライバを選択します。 データソースのプロパティにはユーザー名、パスワードといった項目がありますが指定することはできません。
このままだと接続先サーバー名はデータソース名と同じになります。ほとんどの場合データソース名の、接続先サーバーのコンピュータ名は異なるでしょうから、サーバー名を指定する必要があります。
接続先サーバー名、データベース名は属性として指定します。
odbc_atrubutes
例えば接続先サーバー名を指定するには、属性の追加ボタンをクリックして、表示されるダイアログの名前にServer、値に接続先サーバーのコンピューター名を指定します。同様にデータベース名は名前にDatabase、値にデータベース名を指定します。
最後にログインしたユーザーをここで指定することはできません。属性の名前にLastUserを指定すると動作しなくなります。指定したい場合にはデータソースの設定ではなく、レジストリの設定で行うとよいです。
そのほかの属性名を知りたい場合には、コントロールパネルのODBCから設定を行った後、レジストリ(HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI)の設定を確認してください。ここにデータソース名ごとに実際に設定されている属性が保存されています。実際にはLastUserのように属性として指定できない場合もあるので、レジストリの設定で行ったほうが面倒がないかもしれません。

フォルダを開こうとすると「サーバーの実行に失敗しました。」と表示される

Windows 7でexplorer.exeを起動し、フォルダやコンピュータのプロパティ、コントロールパネル等を開こうとすると、しばらく待った後「 サーバーの実行に失敗しました。」とエラーが表示され、開くことができなくなった。

原因は標準のユーザーフォルダが失われた為でした。 レジストリの”HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders”にある、”Personal”の値が%USERPROFILE%\Desktop\Documentsに書き換わっていました。その後何かの拍子にDesktop\Documentsを削除したためマイドキュメントフォルダがなくなり、その為にExplorer.exeが動作しなくなっていました。

まずはレジストリエディタを起動して、”HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders”にあるPersonalの内容をデフォルトの%USERPROFILE%\Documentsに戻します。次にコマンドプロンプトを開いてmkdir Documentsを実行し、あるべき場所にマイドキュメント用のフォルダを作成しました。

これでフォルダが開けるようになり解決しました。

参考にしたブログ:http://meochika-value.hatenablog.com/entry/2013/07/27/142113

Windows Azure上のWindowsをGUIなしにする

多少でも仮想マシンの利用料を下げるために、GUIを削除して軽くしたいと思います。これで仮想マシンのサイズを下げても、使用メモリ量が減っている分、それなりに使えるようになるはずです。

仮想OSのPowershellのリモート接続を有効にする

GUIを持たないWindowsの操作は主にPowerShellから行う事になります。まずはPowerSheelに対してリモート接続出来る状態にします。Azure仮想OSにRemote Desktopで接続して、PowerShellを管理者権限で起動してEnable-PSRemoteingを実行します。これにより必要なサービスの起動やファイアウォールの設定等が自動で行われますので「[A] Yes to All」を選択して継続してください。

PS C:\Users\username&gt; Enable-PSRemoting
WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable remote management of this computer by using the Windows Remote
Management (WinRM) service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service startup type to Automatic
    3. Creating a listener to accept requests on any IP address
    4. Enabling Windows Firewall inbound rule exceptions for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

クライアントへのSSL証明書のインストール

次にPowerShellのリモート接続で使用するためのSSL証明書のダウンロードとインストールを行います。少々変則的ですがhttps://[仮想マシン名].cloudapp.net:5986のURLにGoogle Chromeを使用してアクセスし、画面赤丸の部分をクリックして証明書を表示します。
chrome#1

次に↓画面の赤丸の部分をクリック。
chrome#2

最後に↓画面の赤丸部分をクリックして、後はウィザードに従ってデフォルト設定のままファイルに保存します。
chrome#3

証明書をダウンロードしたら、証明書ファイルを右クリックしてインストールを選択します。
inport#1

インストールはウィザードに従って勧めていきますが、証明書ストアの選択で「信頼されたルート証明機関」に保存するように設定をしてください。それ以外はデフォルトのままで進めます。
inport#3

クライアントから仮想OSにPowerShellで接続する

これでPowerShellでリモート接続する準備が整ったので、PowerShellを起動して次のコマンドを実行します。プロンプトに接続先コンピュータ名が表示され、リモート接続していることが分かります。

PS C:\Users\username> Enter-PSSession -ComputerName computername.cloudapp.net -Credential knarita -UseSSL
[computername.cloudapp.net]: PS C:\Users\username\Documents>

 

仮想OSからGUIを削除する

せっかくなので、このままAzure仮想OSからGUIをアイインストールしてしまいます。次のコマンドを実行してください。

Uninstall-WindowsFeature Server-Gui-Mgmt-Infra –restart
Success Restart Needed Exit Code      Feature Result
------- -------------- ---------      --------------
True    Yes            SuccessRest... {Windows PowerShell ISE, Graphical Managem...
警告: You must restart this server to finish the removal process.

これで再起動するとGUIが無い状態で起動します。

ちなみにGUIを戻すにはPowerShellから次のコマンドを実行すると、いつでも元に戻せます。

Install-WindowsFeature Server-Gui-Mgmt-Infra,Server-Gui-Shell –restart

記憶域プールでトライブルに見舞われたので覚書2

記憶域プールでトライブルに見舞われたので覚書に続いて、またトラブルに見舞われたので覚え書きを残しておく。

物理ディスクは正常

回復性の低下のエラーが出ているが、物理ディスクセクションは全て正常という状態になった。PowerShellからステータスを確認すると記憶域スペースは確かにDegradedモードになっているが、物理ディスクは全て正常になっている。

PS C:\WINDOWS\system32> Get-VirtualDisk

FriendlyName ResiliencySettingName OperationalStatus HealthStatus IsManualAttach Size
------------ --------------------- ----------------- ------------ -------------- ----
RAID5        Parity                Degraded          Warning      False          3 TB


PS C:\WINDOWS\system32> Get-PhysicalDisk

FriendlyName       SerialNumber         CanPool OperationalStatus HealthStatus Usage            Size
------------       ------------         ------- ----------------- ------------ -----            ----
USBHDD1            0123456789ABCDEF     False   OK                Healthy      Auto-Select   1.82 TB
USBHDD2            0123456789ABCDEF     False   OK                Healthy      Auto-Select   1.82 TB
USBHDD3            00000000354340b930b5 False   OK                Healthy      Auto-Select   1.82 TB

例によってRepair-VirtualDiskは正常に終了して何も教えてくれない。物理ディスクを追加してもDegradedモードから戻らない。前回と異なりchkdsk -rで全セクタのチェックを行っても正常となる。

一見すると正常に使えているのだが、どうも管理領域が破損している物と思われる。仕方が無いので、再び前回と同じように、新たな記憶域スペースを作成してデータを移動したあと、既存の記憶域スペースを削除する事で対応することにした。

せっかく便利な機能なのだから、もうちょっとなんとかならないのだろうか。

Windows 記憶域プールは使わない方が良い

Windowsの記憶域プールは追加投資無しでRAID0~6相当のドライブを作ってデータを保護でき、 シン・プロビジョニング(後からディスクを追加して容量を増やしたりできる)にも対応している。USB3.0を使用すれば多数のドライブを接続できるし、SSDと併用して高速化もできる。設定もGUIベースで簡単にできる。良いことづくめに見えるが、私は他者に記憶域プールの使用は進めない。

管理機能の不足

Windowsの記憶域プールのGUIは管理機能を殆どで提供しない。異常が発生しても「壊れています」以上の詳細な情報はGUIでは把握できない。標準GUIで把握できない事態に陥った場合には、PowerShellというWindowsの独自言語を使ったプログラミングが必須となる。この時点で初心者はお手上げになってしまう。

復旧機能の不足

致命的なことに復旧機能がほとんど提供されていません。ディスクの異常が発生して修復に失敗してしまうと、記憶域プールに作成した仮想ディスクを破棄して作り直す以外の方法がありません。これはPowerShellを使っても同じで、Repair-VirtualDisk(自動修復を実行する)というコマンド以外に普及方法が提供されていません。
一般的なハードウェアRAIDであれば強制的にミラーを解除して再構成をすることも出来るのですが強制的に解除するコマンドは用意されていないのです。これでは上級者でもお手上げになってしまいます。

RAID1対応のNASでも安いものなら2万円を切る価格(除くHDD)で購入できることを考えると、安価なRAID対応NASを使うほうが良いでしょう。

Windows Server 2012でVB6.0のアプリケーションを動かす

Support Statement for Visual Basic 6.0 on Windows Vista, Windows Server 2008, Windows 7, Windows 8 and Windows 8.1, Windows Server 2012, and Windows 10」に互換性に関する情報があります。このページに「Supported Runtime Files to Distribute with Your Application」として動作保証されている再配布ファイルのリスト(全言語共通の物と、日本語など言語固有の物にリストが分かれています。)が公開されています。このファイルを別のフォルダにコピーしていきます。

また上記のリストではOSに付属していることになっていますが、msvbvm60.dllはWindows 2012に含まれていませんので、これもコピーします。

Visual Basic 6.0 Service Pack 6:ランタイム再頒布可能パッケージ (vbrun60sp6.exe)」をダウンロードします。ダウンロードしたVB6.0-KB290887-X86を解凍するとvbrun60sp6.exeが得られます。vbrun60sp6.exeのインストーラはCAB形式の自己解凍ファイルになっています。vbrun60sp6.exeを解凍すると、vbrun60sp6.exeがインストールするいくつかのファイルが得られます。その内の「W95INF32.DLL、W95INF16.DLL、ADVPACK.DLL」はリストに載っていませんがコピーします。

用意したランタイムファイルをWindows Server 2012の適当なフォルダ(ここではC:\VB6Runtimeとします)にコピーします。システムのPATH設定にC:\VB6Runtimeを追加したあと、拡張子がOCXの物についてはregsvr32コマンドを使用してレジストリに登録していきます。C:\Windows\System32にコピーした場合はregsvr32コマンドに失敗します。C:\Windows\System32にはランタイムを置かないで下さい。

「Supported and Shipping in Windows Vista, Windows Server 2008, Windows 7, and Windows 8」に含まれているファイルを誤って配布しないように注意してください。C:\Windows\System32を見てもmsado150.dllやmfc40.dllが が見当たりませんが、規定のインストール先がC:\Windows\SysWOW64やC:\Program Files (x86)\Common Files\System\Ole DBに移っているだけで、きちんと存在しています。無闇に導入してしまうと別の問題を引き起こす恐れがあるので、Supported and Shipping~の対象は絶対にコピーしないでください。

VB6.0のランタイムをインストールするためのフリーウェアがありますが、それらはC:\Windows\System32に複製しても問題なかった時代に設計された物がほとんどのはずです。無闇に利用するとトラブルの原因となり得ます。同じ理由で昔自作したインストーラも危険です。気をつけましょう。