none
Deleted RRS feed

回答

  • パターン1で「開かれていますが切断しますか?」が表示されるのは、実際にそのエラーが出ている(フォルダが開かれている)からと思いますが、本当に何も開いていないのにそのエラーが出る原因については何とも言えません。
    パターン2で「切断しました」が条件に表示されるのは、メッセージを表示する文が、If(Test-Path $Drive) {} を抜けた後にあるからです。
    パターン3は、エラー出力を拾うため、2>&1 を追加して下さい。
    $msg 2>&1 >> $LogFolder\$LogFile
    システムエラー67が残るだけですが・・・。
    システムエラー67の原因究明が本題ではないでしょうか?
    2019年9月26日 21:26
  • 詳細までは把握できていませんが、delete_drive関数内で、ご自身で追加された[System.Windows.Forms.MessageBox]::Show()メソッドの戻り値を変数で受けていない部分があり、そのために関数出力に$true/$false以外に、DialogResultの値が混ざってしまっている可能性があります。関数の出力を条件判定に利用しているので、$true/$false以外の値が出力されないようにしてください。

    また、

        $msg = net use $Drive /delete /yes
        $msg 2>&1 >> $LogFolder\$LogFile 

        if($? -eq $false) # 切断できなかった
        {…

    この部分には複数の問題があります。

    まず、ネイティブコマンド(ここではnet.exe)の実行結果を$msg変数に代入していますが、この記述だと標準出力のみ代入され、標準エラー出力の値は捨てられます。

    net.exe実行時の標準エラー出力を2>&1により標準出力に合成し、その結果を$msgに代入する必要があります。

    次に、$?自動変数は、直前の行のコマンドが失敗した時にFalseが格納されます。しかし、net.exe実行直後に$msg変数の値をファイルにリダイレクトするという行を実行しているので、$?は必ずTrueになってしまっています。

    net.exe実行直後の$?の値を変数に格納しておく等の対策が必要です。

    さらにPowerShellはネイティブコマンドが出力する標準エラー出力を、文字列ではなくErrorRecord型のオブジェクトにラッピングする仕様があるため、2>&1を用いて標準エラー出力に出された文字列を得るには、ToString()メソッドを使う等してstring型に変換する必要もあります。

    (このあたりはPowerShellでネイティブコマンドを扱うときの面倒くさい部分で、個人的にもしんどく感じる部分ではあります…)

    まとめると以下のような方法があるかと思います。

    $msg = net use $Drive /delete /yes 2>&1
    $result = $?
    $msg | foreach {$_.ToString()} >> $LogFolder\$LogFile
    if($result -eq $false)
    {…

    別解としては$LASTEXITCODE変数の値を見て、コマンド成功かどうかを確認する方法があります。$LASTEXITCODE変数は直前のコマンドがExitCodeを出力しない限りは、内容が書き換わらないことを利用します。

    ところで、スクリプトのデバッグはどのようにされていますか?
    PowerShell ISEをご利用であれば、任意の行にブレークポイントを挿入してデバッグを実行すると、ブレークポイントでデバッグを停止し、その時点での変数値を読み取ったり、ステップ実行で1行ずつ実行していくといったことが可能です。

    スクリプトがうまく動作しない場合は、まずはご自身で、変数に意図した値が格納されているか、条件分岐では想定したブロックが実行されているか等をデバッグして確認してみてください。



    2019年9月27日 18:43
    モデレータ

すべての返信

  • net use D: /delete /n

    とすれば、ドライブを開いているときは

    D: との接続にオープン ファイルや未実行のディレクトリ検索があります。

    のメッセージで失敗して $? が False になると思いますががどうでしょう?

    2019年9月19日 3:23
  • なるほど。
    いつも「ダイアログボックス」を略して「ダイアログ」と呼んでいるので、思い込みがありました。

    >何かしらの要因でDドライブ、Lドライブ切断できなかった場合も $? が False になることがあるのではと考えております。

    そう言われると何とも言えませんが、エラーとメッセージが一致しないことはWindowsにもあることですし、レアケースなら心配してもキリがないように思います。
    私の勤め先のIT専門部隊が作る業務ソフトの中に最初にユーザーパスワードを入力してから購入ソフトに引き渡して起動するものがありますが、購入ソフトの不具合で起動に失敗しても「パスワードが間違っています」なんてメッセージです。

    ちなみに、サーバーは触ったことがないのでログインスクリプトがどういうものか分かりませんが、接続の失敗を検知したら、一定時間を置いてリトライはできないのでしょうか?


    2019年9月19日 15:08
  • ネットで調べてみたら、ファイルを開いている全てのプロセスを列挙して調べる方法もあるようです。

    詳しくは調べていませんが取り急ぎ。

    2019年9月19日 21:31
  • net use D: /delete /noを実行した時、ユーザーがファイルを開いている状態だった場合のステータスコードを取得する方法は分かりませんでしたが、「D: との接続にオープン ファイルや未実行のディレクトリ検索があります。」とのメッセージがコンソールに表示されることを判定条件にすればいいのではないでしょうか。

    流れとしては、
    ① net use D: /delete /no を実行する。特に問題なく切断された場合は④へ。

    ②-1 $?がFalseかつ「D: との接続にオープン ファイルや未実行のディレクトリ検索があります。」と表示されたら、ユーザーに切断確認のダイアログを表示

    ②-2 $?がFalseかつ他のエラーが表示された場合(何かしらの要因で切断できなかった場合)は、切断に失敗したとして終了

    ③-1 ダイアログで「はい」が押下されたら、net use D: /delete /yes を実行して強制切断

    ③-2 ダイアログで「いいえ」が押下されたら、そのまま終了する

    ④ D:を共有フォルダに割り当て

    …という流れをコードに起こしてみました。共通のルーチンが多く、上記を反映させるとさらにネストが深くなってしまうので関数に切り出しました。またログ記録は省略しているので適宜補完ください。

    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    
    #ログフォルダの作成、共有フォルダ情報取得などの処理は略
    
    # 指定ドライブを切断する関数
    function delete_drive
    {
        param(
            [string]$Drive
        )
    
        # 切断する
        $msg = net use $Drive /delete /no
    
        if($? -eq $true) # 切断できた
        {
            return $true
        }
    
        if($null -eq $msg -or 
            -not @($msg)[0].Contains("との接続にオープン ファイルや未実行のディレクトリ検索があります。"))
        {
            # 理由は不明だが切断できない
    
            $dialogResult =
                [System.Windows.Forms.MessageBox]::Show("【エラー】${Drive}ドライブの切断に失敗しました")
            return $false
        }
    
        $dialogResult =
            [System.Windows.Forms.MessageBox]::Show(
                "開かれていますが切断しますか?",
                "",
                [System.Windows.Forms.MessageBoxButtons]::YesNo
            )
        
        if($dialogResult -eq [System.Windows.Forms.DialogResult]::No)
        {
            #ドライブを切断せず終了
            return $false
        }
    
        # 強制切断する
        $msg = net use $Drive /delete /yes
    
        if($? -eq $false) # 切断できなかった
        {
            $dialogResult =
                [System.Windows.Forms.MessageBox]::Show("【エラー】${Drive}ドライブの切断に失敗しました")
            return $false
        }
    
        return $true
    }
    
    # ドライブを指定共有フォルダに割り当てる関数
    function map_drive
    {
        param(
            [string]$Drive,
            [string]$Folder
        )
    
        if(Test-Path $Drive) # ドライブが既に割り当てられている
        {
            # ドライブを切断
            $result = delete_drive -Drive $Drive
    
            if($result -eq $false) # 切断に失敗した場合は終了
            {
                return
            }
        }
    
        # ドライブを指定共有フォルダに割り当てる
        $msg = net use $Drive $Folder
    
        if($? -eq $true)
        {
            $dialogResult =
                [System.Windows.Forms.MessageBox]::Show("【情報】${Drive}ドライブを割り当てしました")
        }
        else
        {
            $dialogResult =
                [System.Windows.Forms.MessageBox]::Show("【エラー】${Drive}ドライブを割り当てに失敗しました")
        }
    }
    
    map_drive -Drive "D:" -Folder "\\$value\DATA"
    map_drive -Drive "L:" -Folder "\\$value\MAIL"



    • 編集済み 牟田口大介Moderator 2019年9月19日 22:42 ドライブ割り当てと切断を別の関数にした
    2019年9月19日 22:05
    モデレータ
  • ファイルを開いているプロセスを調べるツールは下記です。

    https://docs.microsoft.com/ja-jp/previous-versions/bb896655(v=msdn.10)?redirectedfrom=MSDN

    これの handle.exe が使えるかと思ったら、共有ドライブはドライブレターでは表示してくれませんでした。
    また、情報収集にも少々時間がかかりますし、これは無いかな、と思います。

    牟田口大介様の方法でも良いと思いますが、プログラミングをやっていると、値でないものはどうにも不安を感じてしまっていけません。(という意味では handle.exe も五十歩百歩)

    net.exe のエラーコードの定義が分かれば良いのですが・・・

    最終手段ですが、WIN32API の WNetCancelConnection2() はどうでしょう?

    https://msdn.microsoft.com/ja-jp/windows/aa385427%28v=vs.80%29?f=255&MSPPError=-2147217396

    これもエラーコードが定義されているものの、言語明瞭意味不明瞭で、ERROR_DEVICE_IN_USE って何?という感じで、完璧に把握できているわけではありません。


    2019年9月20日 15:39
  • >開かれていないにもかかわらず、「との接続にオープンファイルや未実行のディレクトリ検索があります。切断を続行し、強制的に閉じますか?(Y/N)  [N]:」が表示された場合は、Microsoftのバグになると思うのでその点は考慮する必要はないと思っています。

    どうでも良いことですが、バグに限らず、バックグラウンドプロセスが開くケースもあると思います。

    Administrator作業でドメインアカウントで共有フォルダにに接続した後、長時間処理のため一旦離れる際に、Administrator のパスワードを知っている人が結構いるので、net use * /d で切断するのですが、共有フォルダにアクセスしたフォルダは全て閉じているのにしばしば切断されないことがあって悩まされます。Explorerを強制終了しても切断されなかったので、アンチウィルスか監視ソフトを疑っています。

    2019年9月20日 15:44
  • > 「map_drive」関数で$dialogResultにセットしていますが、if文での判定に使用していないように見られるのですが、「map_drive」関数で$dialogResultにセットは必要になりますでしょうか。

     [System.Windows.Forms.MessageBox]::Show()メソッドは、DialogResult列挙型の値を返却します。そのため、変数に代入しないとDialogResultの値が関数の出力として、コンソールに表示されてしまいます。OKボタンを押下した場合は「OK」と表示されます。それを防ぐのが目的ですね。

     [System.Windows.Forms.MessageBox]::Show() | Out-Null

    のようにすることでも出力を抑制できますが、変数代入の方が一貫性があって良いかと思いました。

    2019年9月21日 2:10
    モデレータ
  • >システムエンジニアにサーバーを確認してもらうと他のユーザーが開いている場合もあるのですが、
    >DドライブとLドライブは個人用に割り当てをしているネットワークドライブで、他のユーザーがアクセスすることなないので、

    これ、矛盾してませんか?(繋がってないなら良いですが)

    >開いているユーザーがいない場合もあり、そのときはパソコンの再起動を行ってもらうと改善することもありますので、システムがつかんでしまっているときがあるのではと思っています。

    システムであろうと、何らかのアカウントで開いているはずですので、探し方がは悪いのでは?と思ってしまいます。

    2019年9月21日 7:17
  • >ファイルが見当たらないという問い合わせがときどきあります。
    >〜原因不明で終わらせてしまう

    脱線させて済みませんが、見過ごせないなと思いまして。
    私もシステム管理をやっていて、誤削除/上書き等で泣きつかれることもよくありますが、シャドウコピーは取られているでしょうか?
    ハードエラー時は別媒体へのバックアップでないと役に立ちませんが、ヒューマンエラー対策としてはシャドウコピーの手軽さは非常に強力です。

    しかし、保存場所や移動したことを忘れた可能性もあると思いますし、複数人で共有しているフォルダでは、誰かが意図的に移動した可能性もあるので、復元前の調査をどこまでするか難しいところです。

    私もやらなきゃと思いつつ3年くらい先延ばしにしてしまいましたが、私が触れる範囲の Windows の共有フォルダーは監査ログの記録を始めようと思います。

    参考までに、先程、下記を読みました。
    https://www.netassist.ne.jp/blog/?p=10733

    そんなの既にやっている、ということでしたら済みません。
    私の所属部門では、私が部内のシステム管理者になってサーバー管理部門に要求するまでシャドウコピーはとっていませんでした。


    2019年9月22日 1:01
  • シャドウコピーは取られているのですね。

    失礼しました。

    2019年9月22日 7:51
  • ネットワークパスが間違っているならシステムエラー53になると思うのですが。

    システムエラー67は確かに Workstaion サービスが起動していないと発生するエラーのようですが、ネットワークパスの最初の \\ を \ にしても出るようです。スクリプトが正しく記述されているかご確認頂けますでしょうか?

    2019年9月25日 14:43
  • Deleted
    2019年9月26日 12:17
  • パターン1で「開かれていますが切断しますか?」が表示されるのは、実際にそのエラーが出ている(フォルダが開かれている)からと思いますが、本当に何も開いていないのにそのエラーが出る原因については何とも言えません。
    パターン2で「切断しました」が条件に表示されるのは、メッセージを表示する文が、If(Test-Path $Drive) {} を抜けた後にあるからです。
    パターン3は、エラー出力を拾うため、2>&1 を追加して下さい。
    $msg 2>&1 >> $LogFolder\$LogFile
    システムエラー67が残るだけですが・・・。
    システムエラー67の原因究明が本題ではないでしょうか?
    2019年9月26日 21:26
  • 詳細までは把握できていませんが、delete_drive関数内で、ご自身で追加された[System.Windows.Forms.MessageBox]::Show()メソッドの戻り値を変数で受けていない部分があり、そのために関数出力に$true/$false以外に、DialogResultの値が混ざってしまっている可能性があります。関数の出力を条件判定に利用しているので、$true/$false以外の値が出力されないようにしてください。

    また、

        $msg = net use $Drive /delete /yes
        $msg 2>&1 >> $LogFolder\$LogFile 

        if($? -eq $false) # 切断できなかった
        {…

    この部分には複数の問題があります。

    まず、ネイティブコマンド(ここではnet.exe)の実行結果を$msg変数に代入していますが、この記述だと標準出力のみ代入され、標準エラー出力の値は捨てられます。

    net.exe実行時の標準エラー出力を2>&1により標準出力に合成し、その結果を$msgに代入する必要があります。

    次に、$?自動変数は、直前の行のコマンドが失敗した時にFalseが格納されます。しかし、net.exe実行直後に$msg変数の値をファイルにリダイレクトするという行を実行しているので、$?は必ずTrueになってしまっています。

    net.exe実行直後の$?の値を変数に格納しておく等の対策が必要です。

    さらにPowerShellはネイティブコマンドが出力する標準エラー出力を、文字列ではなくErrorRecord型のオブジェクトにラッピングする仕様があるため、2>&1を用いて標準エラー出力に出された文字列を得るには、ToString()メソッドを使う等してstring型に変換する必要もあります。

    (このあたりはPowerShellでネイティブコマンドを扱うときの面倒くさい部分で、個人的にもしんどく感じる部分ではあります…)

    まとめると以下のような方法があるかと思います。

    $msg = net use $Drive /delete /yes 2>&1
    $result = $?
    $msg | foreach {$_.ToString()} >> $LogFolder\$LogFile
    if($result -eq $false)
    {…

    別解としては$LASTEXITCODE変数の値を見て、コマンド成功かどうかを確認する方法があります。$LASTEXITCODE変数は直前のコマンドがExitCodeを出力しない限りは、内容が書き換わらないことを利用します。

    ところで、スクリプトのデバッグはどのようにされていますか?
    PowerShell ISEをご利用であれば、任意の行にブレークポイントを挿入してデバッグを実行すると、ブレークポイントでデバッグを停止し、その時点での変数値を読み取ったり、ステップ実行で1行ずつ実行していくといったことが可能です。

    スクリプトがうまく動作しない場合は、まずはご自身で、変数に意図した値が格納されているか、条件分岐では想定したブロックが実行されているか等をデバッグして確認してみてください。



    2019年9月27日 18:43
    モデレータ
  • >> $msg 2>&1 >> $LogFolder\$LogFile

    失礼しました。これは恥ずかしい・・・。
    既に牟田口大介様コメントで直されてますが、$msg の実行結果の標準エラー出力拾ってもしょうがないので、net.exe の標準エラー拾わないとダメです。

    >しかし、net.exe実行直後に$msg変数の値をファイルにリダイレクトするという行を実行しているので、$?は必ずTrueになってしまっています。

    確かに、これも詰めが甘くて済みません。

    ところで、D: L: に対するTest-Path に成功しても一旦切断されていますが、
    (1) ユーザが勝手に別のネットワークリソースに割り当てたもの
    を想定されているのでしょうか?
    (2) ログオンスクリプトが成功したもの
    (3) ログオンスクリプトの失敗の処置としてユーザーがルールに従って手動で割り当てたもの
    だとしたら、Test-Pathに成功する場合でも、何か不具合があるのでしょうか?

    全く別のアプローチとして、ドライブレターは割り当てないでUNCのみの運用への切り替えは不可能でしょうか?

    私の勤め先では20年くらい前からUNCで定着しています。

    ただし、未だにUNCに対応してくれないアプリがひとつだけあり、それはバッチ起動として、ドライブレターを割り当てています。

    2019年9月28日 0:39
  • >自分が作成したスクリプトでは、D/Lドライブの切断、D/Lドライブの割り当てが行えており、

    であるなら、それを元に改修した方が早いでしょう。
    改修のポイントは

    $msg = net use D: /delete /no

    のようにして net コマンドの出力を変数で受けて、 

    if($null -eq $msg -or 
            -not @($msg)[0].Contains("との接続にオープン ファイルや未実行のディレクトリ検索があります。"))

    で判定です。
    $null -eq $msg で比較しているのは、出力が空で $msg が Null の場合、Contains が失敗するからです。
    -or は左辺が $True であれば、右辺は評価されません。


    Powershell ISE は、スクリプトファイル(.ps1) を右クリックして「編集」で起動できます。(スタートからでも起動できます)
    一時停止したい行で右クリックして「ブレークポイントの設定/解除」でブレークポイントを設定後、F5キーで実行すると設定した行で止まり、一時停止中に調べたい変数にマウスポインタを合わせると、変数の内容が表示されます。
    1行ずつ実行するには、F10でステップオーバー(関数の中は止めない)、F11でステップイン(関数の中に入って止める)、F12でステップアウト(関数を抜ける)を使います。

    2019年9月29日 0:34