none
タスクスケジューラから実行した際のコマンドのリターンコードについて RRS feed

  • 質問

  • いつもお世話になっております。

    PowerShellにてバッチを作成しております。

    その際に、コマンドのリターンコードに応じてログを出力しています。

    ‐‐該当の処理‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

    #ゴミ箱の中身を削除
    Clear-RecycleBin -Force;
    if(!$?) {
     $rtnCd=$False;
       Write-Log $Error[0].exception;
    }

    PowerShellを手動実行した場合、TRUEがリターンされるのですが、タスクスケジューラから実行した場合、

    FALSEがリターンされ、以下のエラーメッセージが表示されます。(処理自体は正常に行われています。)

    スケジューラは、最上に特権で実行するようにしております。

    原因等わかる方いらっしゃいますでしょうか。

    「System.ComponentModel.Win32Exception (0x80004005): 指定されたファイルが見つかりません。」 



    • 編集済み oga_rei 2019年10月19日 15:03
    2019年10月19日 14:58

回答

  • 不具合かどうかはさておき、現時点では「Clear-RecycleBin に対する成否判定は行わない」という選択肢しか無さそうな気がします。(-Force の代わりに -Confirm:$False を用いた場合も、タスクスケジューラーからの実行時には同様のエラーが発生しました)

     

    どうしても成否判定が必要なら、「ごみ箱を空にする API を自力で呼びなおす」という手が使えるかと思ったのですが……これも厄介な問題を抱えていました。

    ごみ箱を空にする際に呼び出されるのは SHEmptyRecycleBinW API です。

    PowerShell のソースコード 226 行目 によれば、-force 指定時には、同 API の処理結果を Marshal.GetLastWin32Error メソッドで成否判定するようになっていたため、これが ERROR_FILE_NOT_FOUND を通知させる要因になっている可能性があります。手元で確認した限り、この時点での GetLastWin32Error は、2 (ERROR_FILE_NOT_FOUND)を返しますし、それを Win32Exception に渡すと、今回のエラーと同じメッセージが出力されたというのが、原因と推察した理由です。

    # 『System.ComponentModel.Win32Exception (0x80004005): 指定されたファイルが見つかりません。』
    $ERROR_FILE_NOT_FOUND = 2;
    (New-Object System.ComponentModel.Win32Exception $ERROR_FILE_NOT_FOUND).ToString();


    とはいえ同ソースで API を宣言している 254 行目を見ると、 DllImportAttribute.SetLastError が指定されていないように見えます。その場合、ここの GetLastWin32Error が返す値は、直前の SHEmptyRecycleBinW に対する GetLastError API の結果ではなく、さらにその前に呼ばれた処理のエラーを指し示すはずなので、現状の Clear-RecycleBin の実装はこのソースと同一ではなく、実際には今回の事象とは無関係かもしれません。

     

    いずれにせよ SDK の記述を見る限り、SHEmptyRecycleBinW の処理結果は HRESULT によって示されるものであるようで、GetLastError で判定できるという記載は見つけられませんでした。そのため、ここで GetLastWin32Error メソッドあるいは GetLastError を呼ぶことは undocumented な判定方法であるように思えます。かといって、ごみ箱が空の時に SHEmptyRecycleBinW を呼び出した場合、OS 側から COM 例外 E_UNEXPECTED “致命的なエラーです。” が返却されてしまう実装になっている以上、自力で API を呼び出す場合においても、戻り値の HRESULT をそのまま例外として扱うわけにもいかないので、成否判断を行うことができません。なかなか根の深そうな問題です。


    以下、C# からの API 呼び出し実験。

    using System;
    using System.Runtime.InteropServices;
    public class Program
    {
        static void Main()
        {
            string drivePath = null;
            int lastError1 = Marshal.GetLastWin32Error();
    
            // 戻り値として、通常は 0x0 (=S_OK) が返されるのだが、
            // ごみ箱が空の場合に呼び出した場合は 0x8000FFFF (=E_UNEXPECTED) が返される。
            uint result = NativeMethod.SHEmptyRecycleBin(IntPtr.Zero, drivePath,
                                NativeMethod.RecycleFlags.SHERB_NOCONFIRMATION |
                                NativeMethod.RecycleFlags.SHERB_NOPROGRESSUI |
                                NativeMethod.RecycleFlags.SHERB_NOSOUND);
    
            // DllImport 属性で SetLastError = true が指定されていた場合、
            // 常に戻り値 2 (=ERROR_FILE_NOT_FOUND) が返された。
            int lastError = Marshal.GetLastWin32Error();
        }
    
        internal static class NativeMethod
        {
            internal enum RecycleFlags : uint
            {
                SHERB_NOCONFIRMATION = 0x00000001,
                SHERB_NOPROGRESSUI = 0x00000002,
                SHERB_NOSOUND = 0x00000004
            }
    
            //[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
            [DllImport("Shell32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "SHEmptyRecycleBinW", ExactSpelling = true)]
            internal static extern uint SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, RecycleFlags dwFlags);
        }
    }

    2019年10月24日 1:30

すべての返信

  • ごみ箱を空にする操作は、ユーザーがログオンしている必要があります。

    タスクのプロパティで、「ユーザーがログオンしているときのみ実行する」にチェックを入れてみて下さい。

    2019年10月19日 16:57
    モデレータ
  • お世話になっております。

    ご教授頂きました通り、「ユーザがログオンしているときのみ実行する」にチェックを入れてみて実行しましたが、

    結果は変わりませんでした。

    以下、タスクスケジューラの設定(関係ありそうなところの抜粋しました。)です。

    ■全般

    ‐ユーザがログオンしているときのみ実行する。

    ‐最上位の特権で実行する

    ■操作

    ‐プログラムの開始

    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command 起動するps1ファイルの絶対パス

    OSのバグなのかなとも思っているのですが、本件のような事象をご存じの方いらっしゃいますでしょうか。

    ちなみにOSは、Windows10 homeです。

    2019年10月22日 14:06
  • Write-Log コマンドレットの仕様が分からなかったのですが、当方で試してみるると
    Clear-Recyclebin コマンドレットが実行されて削除処理が行われた直後に、何故か追加のエラーが発生していました。

    Clear-RecycleBin : 指定されたファイルが見つかりません。
    発生場所 C:\example\test.ps1:2 文字:1
    + Clear-RecycleBin -Force;
    + ~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (RecycleBin:String) [Clear-RecycleBin]、Win32Exception
        + FullyQualifiedErrorId : FailedToClearRecycleBin,Microsoft.PowerShell.Commands.ClearRecycleBinCommand
    
    System.ComponentModel.Win32Exception (0x80004005): 指定されたファイルが見つかりません。

     

    エラーメッセージで調べてみたらこんな話が見つかりました。(内容までは読み込んでいないので、今回の件に関連する内容かどうかまでは分かりません)

    • PowerShell issues #6743 - "Misuse of `SHEmptyRecycleBin` in `Clear-RecycleBin` cmdlet"

    2019年10月23日 1:22
  • 不具合かどうかはさておき、現時点では「Clear-RecycleBin に対する成否判定は行わない」という選択肢しか無さそうな気がします。(-Force の代わりに -Confirm:$False を用いた場合も、タスクスケジューラーからの実行時には同様のエラーが発生しました)

     

    どうしても成否判定が必要なら、「ごみ箱を空にする API を自力で呼びなおす」という手が使えるかと思ったのですが……これも厄介な問題を抱えていました。

    ごみ箱を空にする際に呼び出されるのは SHEmptyRecycleBinW API です。

    PowerShell のソースコード 226 行目 によれば、-force 指定時には、同 API の処理結果を Marshal.GetLastWin32Error メソッドで成否判定するようになっていたため、これが ERROR_FILE_NOT_FOUND を通知させる要因になっている可能性があります。手元で確認した限り、この時点での GetLastWin32Error は、2 (ERROR_FILE_NOT_FOUND)を返しますし、それを Win32Exception に渡すと、今回のエラーと同じメッセージが出力されたというのが、原因と推察した理由です。

    # 『System.ComponentModel.Win32Exception (0x80004005): 指定されたファイルが見つかりません。』
    $ERROR_FILE_NOT_FOUND = 2;
    (New-Object System.ComponentModel.Win32Exception $ERROR_FILE_NOT_FOUND).ToString();


    とはいえ同ソースで API を宣言している 254 行目を見ると、 DllImportAttribute.SetLastError が指定されていないように見えます。その場合、ここの GetLastWin32Error が返す値は、直前の SHEmptyRecycleBinW に対する GetLastError API の結果ではなく、さらにその前に呼ばれた処理のエラーを指し示すはずなので、現状の Clear-RecycleBin の実装はこのソースと同一ではなく、実際には今回の事象とは無関係かもしれません。

     

    いずれにせよ SDK の記述を見る限り、SHEmptyRecycleBinW の処理結果は HRESULT によって示されるものであるようで、GetLastError で判定できるという記載は見つけられませんでした。そのため、ここで GetLastWin32Error メソッドあるいは GetLastError を呼ぶことは undocumented な判定方法であるように思えます。かといって、ごみ箱が空の時に SHEmptyRecycleBinW を呼び出した場合、OS 側から COM 例外 E_UNEXPECTED “致命的なエラーです。” が返却されてしまう実装になっている以上、自力で API を呼び出す場合においても、戻り値の HRESULT をそのまま例外として扱うわけにもいかないので、成否判断を行うことができません。なかなか根の深そうな問題です。


    以下、C# からの API 呼び出し実験。

    using System;
    using System.Runtime.InteropServices;
    public class Program
    {
        static void Main()
        {
            string drivePath = null;
            int lastError1 = Marshal.GetLastWin32Error();
    
            // 戻り値として、通常は 0x0 (=S_OK) が返されるのだが、
            // ごみ箱が空の場合に呼び出した場合は 0x8000FFFF (=E_UNEXPECTED) が返される。
            uint result = NativeMethod.SHEmptyRecycleBin(IntPtr.Zero, drivePath,
                                NativeMethod.RecycleFlags.SHERB_NOCONFIRMATION |
                                NativeMethod.RecycleFlags.SHERB_NOPROGRESSUI |
                                NativeMethod.RecycleFlags.SHERB_NOSOUND);
    
            // DllImport 属性で SetLastError = true が指定されていた場合、
            // 常に戻り値 2 (=ERROR_FILE_NOT_FOUND) が返された。
            int lastError = Marshal.GetLastWin32Error();
        }
    
        internal static class NativeMethod
        {
            internal enum RecycleFlags : uint
            {
                SHERB_NOCONFIRMATION = 0x00000001,
                SHERB_NOPROGRESSUI = 0x00000002,
                SHERB_NOSOUND = 0x00000004
            }
    
            //[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
            [DllImport("Shell32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "SHEmptyRecycleBinW", ExactSpelling = true)]
            internal static extern uint SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, RecycleFlags dwFlags);
        }
    }

    2019年10月24日 1:30
  • いつもお世話になっております。

    ご丁寧なご回答ありがとうございます。

    必ずしも成否判定は、必ずしも必要ではないので、

    行わないようしたいと思います。

    ありがとうございました。

    2019年10月24日 12:50