RegSaveKeyによるレジストリのエクスポート方法について

解決


どら  2008-07-17 03:28:05  No: 68717

こんにちは。

RegSaveKeyと、RegReplaceKeyを使って、

  HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings

以下のキーや値をエクスポート、インポートするようなものを作っています。
で、エクスポートする方で早速躓いてしまいました。

エクスポート用の関数として、以下のようなサンプル関数を作ってみました。

//インターネットの設定をエクスポートする関数
BOOL ExportInternetSettings(char *RegFileName)
{
  HKEY l_hKey;
  DWORD dwDisposition;
  LPVOID lpMessageBuffer;

  //レジストリを開く
  if(RegCreateKeyEx(HKEY_CURRENT_USER, INTERNET_CONNECTION_KEY, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &l_hKey, &dwDisposition) !=  ERROR_SUCCESS)
//  if(RegOpenKeyEx(HKEY_CURRENT_USER, INTERNET_CONNECTION_KEY, 0, KEY_ALL_ACCESS, &l_hKey) !=  ERROR_SUCCESS)
  {
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
    MessageBox(NULL, (LPCSTR)lpMessageBuffer, "エラー", MB_OK);
    LocalFree( lpMessageBuffer );
    return FALSE;
  }

  //レジストリをエクスポート
  if(RegSaveKey(l_hKey, RegFileName, NULL) != ERROR_SUCCESS)
  {
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
    MessageBox(NULL, (LPCSTR)lpMessageBuffer, "エラー", MB_OK);
    LocalFree( lpMessageBuffer );
    RegCloseKey(l_hKey);
    return FALSE;
  }

  RegCloseKey(l_hKey);
  return TRUE;
}

実行してみたところ、RegSaveKeyで失敗をしているのですが・・・
FormatMessageでGetLastErrorの情報を取得しても「正常に終了」と・・・

MSDNなどを見てもわからず・・・
どなたかご存じの方、何がいけないかご教授いただけませんでしょうか?

ちなみに、開発環境はWindows XP SP2 + VS.NET 2005 Team + 最新Windows SDK です。

よろしくお願いいたします。


どら  2008-07-17 03:33:48  No: 68718

済みません、ソースそのまま貼り付けたら見づらい上に、一つ定義が抜けていました・・・

ソース部分だけ訂正します。

#define INTERNET_CONNECTION_KEY "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"  //IE設定保存先レジストリキー

//インターネットの設定をエクスポートする関数
BOOL ExportInternetSettings(char *RegFileName);

//WinMain関数
int APIENTRY WinMain(HINSTANCE hInstCurrent, HINSTANCE hInstPrev, LPTSTR lpszCmdLine, int nCmdShow)
{
    ・・・省略(ここでエクスポート関数を実行)
}
//インターネットの設定をエクスポートする関数
BOOL ExportInternetSettings(char *RegFileName)
{
   HKEY l_hKey;
   DWORD dwDisposition;
   LPVOID lpMessageBuffer;

   //レジストリを開く
   if(RegCreateKeyEx(HKEY_CURRENT_USER, 
                     INTERNET_CONNECTION_KEY, 
                     0, NULL, 
                     REG_OPTION_NON_VOLATILE, 
                     KEY_ALL_ACCESS, 
                     NULL,
                     &l_hKey, &dwDisposition) !=  ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                     NULL,
                     GetLastError(),
                     MAKELANGID(LANG_NEUTRAL,
                     SUBLANG_DEFAULT),
                     (LPTSTR) &lpMessageBuffer,
                     0,
                     NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      return FALSE;
   }

   //レジストリをエクスポート
   if(RegSaveKey(l_hKey, RegFileName, NULL) != ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                     NULL,
                     GetLastError(),
                     MAKELANGID(LANG_NEUTRAL,
                     SUBLANG_DEFAULT),
                     (LPTSTR) &lpMessageBuffer,
                     0,
                     NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      RegCloseKey(l_hKey);
      return FALSE;
   }

   RegCloseKey(l_hKey);
   return TRUE;
}

よろしくお願いいたします。


シャノン  2008-07-17 03:47:32  No: 68719

RegSaveKeyのページ
http://msdn.microsoft.com/en-us/library/ms724917(VS.85).aspx
を見ますと、どこにも「失敗時には GetLastError を使え」と書いてありません。
関数の戻り値を FormatMessage に与えてみてはいかがでしょうか。


どら  2008-07-17 19:29:35  No: 68720

>シャノンさん

レスありがとうございます。
まさにその通りですね・・・。

で、確認してみたところ、「クライアントは要求された特権を保有していませ
ん。」だそうです。

でも、実行ユーザはローカル管理者を持ってますし・・・どういうことなんで
しょ?

ちなみに、実際にはUsers権限のアカウントがログオンしているときに、HKCU
の値をエクスポート・インポート(リストア)しようと思っているのですが・・

コマンドから実行するしかないんですかね・・・?


シャノン  2008-07-17 20:20:10  No: 68721

> 「クライアントは要求された特権を保有していません。」だそうです。

そんな気がしていました。
同じページに、こうも書いてあります。

> The calling process must have the SE_BACKUP_NAME privilege enabled.

ということで、この特権を有効にする必要があります。

特権には、「持っている/持っていない」と「有効/無効」という状態があり、「持っていて有効」「持っていて無効」「持っていない」の3通りの組み合わせがあります(持っていない場合は有効にできません)。
で、Administrators と Backup Operators に属するユーザはこの特権を持っているので有効にできますが、Users は持っていないので有効にできません。
従って、Users ではこれらの関数は使用できないということになります。
設定次第では Users にこの特権を持たせることもできるのですが、そうすると、HKCU のみならず、システム上の全ファイルに対して(ACLに関係なく)読み取りアクセス権を持ってしまうので、過ぎた権限と言えます。

ちなみに、RegSaveKey が生成するのはバイナリファイルで、RegEdit のエクスポートで生成できるようなテキストファイルではありません。
RegEnumKeyEx / RegQueryValueEx / WritePrivateProfileString 等の API を使って自分でロジックを組めば、RegEdit を模倣した ini 形式のファイルを作ることは可能です。
レストアするにも RegSetValueEx 等を何度も呼ばなければいけないので面倒ではありますが、これらの API を使うだけなら、SE_BACKUP_NAME 特権は要りません。
個人的にはそっちをおすすめしますが、いかがでしょうか?


シャノン  2008-07-17 20:22:53  No: 68722

外部コマンドを使ってもいいのなら、reg.exe が使えます。
この save / restore コマンドが RegSaveKey / RegLoadKey で、export / import コマンドが、RegQueryValueEx / RegSetValueEx 等で実現されています。


どら  2008-07-17 21:33:37  No: 68723

シャノンさん

ありがとうございます。
やっぱりそうなのですね・・・

作成しているツールでは、名前付きパイプを使ってシステムサービスに信号を
送って別の処理をしてもらっているところがあるので、そこを使ってできない
か試してみて(ローカルシステムアカウントで実行した場合に、現在のログオ
ンアカウントのHKCUを取得できるかが鍵ですね・・・)、それでダメであれば、
コマンドでエクスポート&インポートするようにします。

いつもありがとうございます。


シャノン  2008-07-17 22:54:54  No: 68724

LoadUserProfile 関数を使えば、他のユーザの HKCU キーにアクセスできそうです。
あるいは、RegOpenKeyEx( HKEY_CURRENT_USER, NULL, ... ) で取得したハンドルをパイプを通じて渡してもいいかも。

> それでダメであれば、コマンドでエクスポート&インポートするようにします。

一般ユーザ権限で reg.exe save が成功するかどうか試してみた方がいいと思います。


どら  2008-07-18 02:15:13  No: 68725

シャノンさん

ありがとうございました!!
あの後、一つ参考ページを見つけたのですが
http://support.microsoft.com/kb/128731/ja )

実は、今のところ管理者権限でもうまく言っていなかったんです。
んで、上記のサイトを参考に、以下のようにエクスポート、インポートの関数
を修正しました。

//インターネットの設定をエクスポートする関数
BOOL ExportInternetSettings(char *RegFileName)
{
   HKEY l_hKey;
   DWORD dwDisposition;
   LPVOID lpMessageBuffer;
   LONG l_Ret;
   HANDLE l_hToken;
   LUID l_luid;
   TOKEN_PRIVILEGES l_tp;

   //RegSaveKeyを行う為の特権を有効する
   //まずはトークンを開く
   if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &l_hToken))
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "OpenPricessToke実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      return FALSE;
   }
   //次にLUID取得
   if(!LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &l_luid))
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "LookupPrivilegeValue実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      CloseHandle(l_hToken);
      return FALSE;
   }
   //トークン内の特権を有効にする
   l_tp.PrivilegeCount = 1;
   l_tp.Privileges[0].Luid = l_luid;
   l_tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
   AdjustTokenPrivileges(l_hToken, FALSE, &l_tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL );
   if(GetLastError() != ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "AdjustTokenPrivileges実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      return FALSE;
   }

   //レジストリを開く
   l_Ret = RegCreateKeyEx(HKEY_CURRENT_USER, INTERNET_CONNECTION_KEY, 0, NULL, REG_OPTION_BACKUP_RESTORE, KEY_QUERY_VALUE, NULL, &l_hKey, &dwDisposition);
   if(l_Ret !=  ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, l_Ret, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "RegCreateKeyEx実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      return FALSE;
}

   //レジストリをエクスポート
   l_Ret = RegSaveKey(l_hKey, RegFileName, NULL);
   if(l_Ret != ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, l_Ret, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "RegSaveKey実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      RegCloseKey(l_hKey);
      return FALSE;
   }

   RegCloseKey(l_hKey);
   AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
   CloseHandle(l_hToken);

   return TRUE;
}

//インターネットの設定をインポートする関数
BOOL ImportInternetSettings(char *RegFileName)
{
   HKEY l_hKey;
   DWORD dwDisposition;
   LPVOID lpMessageBuffer;
   LONG l_Ret;
   HANDLE l_hToken;
   LUID l_luid;
   TOKEN_PRIVILEGES l_tp;

   //RegSaveKeyを行う為の特権を有効する
   //まずはトークンを開く
   if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &l_hToken))
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "OpenPricessToke実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      return FALSE;
   }
   //次にLUID取得
   if(!LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &l_luid))
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "LookupPrivilegeValue実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      CloseHandle(l_hToken);
      return FALSE;
   }
   //トークン内の特権を有効にする
   l_tp.PrivilegeCount = 1;
   l_tp.Privileges[0].Luid = l_luid;
   l_tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
   AdjustTokenPrivileges(l_hToken, FALSE, &l_tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL );
   if(GetLastError() != ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "AdjustTokenPrivileges実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      return FALSE;
   }

   //レジストリを開く
   l_Ret = RegCreateKeyEx(HKEY_CURRENT_USER, INTERNET_CONNECTION_KEY, 0, NULL, REG_OPTION_BACKUP_RESTORE, KEY_QUERY_VALUE, NULL, &l_hKey, &dwDisposition);
   if(l_Ret !=  ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, l_Ret, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "RegCreateKeyEx実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      return FALSE;
   }

   //レジストリをインポート
   l_Ret = RegRestoreKey(l_hKey, RegFileName, REG_FORCE_RESTORE);
   if(l_Ret != ERROR_SUCCESS)
   {
      FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, l_Ret, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL);
      MessageBox(NULL, (LPCSTR)lpMessageBuffer, "RegRestoreKey実行エラー", MB_OK);
      LocalFree( lpMessageBuffer );
      RegCloseKey(l_hKey);
      AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
      CloseHandle(l_hToken);
      return FALSE;
   }

   RegCloseKey(l_hKey);
   AdjustTokenPrivileges(l_hToken, TRUE, NULL, 0, NULL, NULL);
   CloseHandle(l_hToken);

   return TRUE;
}

このように、特権を有効にしなければならなかったのですね・・・
これで、とりあえず管理者ではできそうです!!

後は、シャノンさんのアイデアのパイプにキーのハンドルを渡す方法を試して
みます!!
ナイスなアイデア、ありがとうございます☆

この場合、パイプで渡したキーのハンドルは、どちらがわでCloseHandleして
も大丈夫なんですかね?

コマンドの方は、おっしゃるとおりなので、適当にバッチでもつくって試して
みます。

個人的にはAPIでバイナリで出力した方が、一見中身がわからないのでいいか
な〜とは思っています。

本当にありがとうございました!!


シャノン  2008-07-18 19:18:46  No: 68726

あー…ハンドルってプロセスに関連付けられてるんでしたっけ?
だとしたらダメか。


シャノン  2008-07-18 20:22:29  No: 68727

サービス側で LoadUserProfile を呼び出すにしても、アプリ側のトークンが必要。
いずれにせよ、アプリ側で開いたハンドルをサービス側に渡さなければならないんだけど、原則としてカーネルハンドルはプロセスに固有なので、そのままではサービス側では使えない。
そこで、パイプを通じてアプリのプロセス ID も一緒に渡して、サービス側で DuplicateHandle する、という方法がある。

もしくは、ImpersonateNamedPipeClient を使えば、もっと簡単にできるかもしれない。
この関数でサービス側がアプリ側のユーザーを偽装して RegOpenKeyEx で HKCR を開いて、RevertToSelf で元に戻ってから RegSaveKey でいけるかなぁ。


どら  2008-07-19 00:35:56  No: 68728

シャノンさん

マニアックな質問にいつも丁寧に回答いただき、感謝します。
すでにパイプで渡すデータの構造体や初期化関数などはここ以前にできてしま
っているので、まずはそれを壊さずにできそうなImpersonateNamedPipeClient
を試してみようかと思います。

初心者に毛が生えたような分際で、こんなマニアックな質問をして済みません。

そしていつもありがとうございます!!

結果はまたご報告しようと思います!!


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加