none
xp_cmdshellの戻り値について RRS feed

  • 質問

  • マニュアルによると、

    0 (成功) または 1 (失敗)

     

    私が実行してみたら、リターン値は‐1でした。

     

    ソースコード:

    xp_cmdshell '外部EXE 外部EXEパラメータ'

     

    外部EXEというのが、自分がVB2008で作ったEXEファイルです。

     

    ‐1はなぜでしょうか。

    2008年9月12日 2:16

回答

  • アプリケーションを終了するのに Process.GetCurrentProcess.Kill() を使っているんですか?

    多分それが原因です。

     

    検証するために、Form_Loadでプログラムを終了だけのプログラムを作成し、検証してみました。

    パターン1:Process.GetCurrentProcess.Kill()で終了する。

     xp_cmdshellの戻り値は-1

     

    パターン2:Application.Exit()で終了する。

     xp_cmdshellの戻り値は0

     

    パターン3:Me.Close()
           Me.Dispose()で終了する。

     xp_cmdshellの戻り値は0

     

    おそらくは、xp_cmdshellが起動したプログラムの戻り値を受け取る前にプロセスが終了したために

    0でも1でもなく、(意図としては起動したプロセスが異常終了したというエラーとして)-1を返したものと思われます。

     

    # 老婆心ですが、Process.GetCurrentProcess.Kill() はマルチスレッドで他スレッドを含めて強制終了する場合以外、

    # 使わない方がいいような・・・。

    # あとxp_cmdshellもセキュリティ上、あまり使わない方がいいような。

    # (まあ、設定次第ですけど)

    2008年9月16日 3:02

すべての返信

  • SQL Serverのバージョンはいくつでしょうか?

     

    自分がテストしたところ、0(成功)か1(失敗)が返って来ませんでした。

    以下にMSDNのサンプルスクリプトを参考にしてテストに使用したT-SQLのコードを記述します。

     

    -- 成功する場合

    DECLARE @result int
    EXEC @result = xp_cmdshell 'dir *.exe'
    SELECT @result

    -- @resultの値は0

     

     

    -- dirの綴りをわざと間違えて、失敗する場合

    DECLARE @result int
    EXEC @result = xp_cmdshell 'dor *.exe'
    SELECT @result

    -- @resultの値は1

     

    また、

    > 外部EXEというのが、自分がVB2008で作ったEXEファイルです。

    とありますが、これはGUIを持ったアプリケーションでしょうか? それともコンソールアプリケーションでしょうか?

     

    というのも、試しにGUIを持ったアプリケーション(メモ帳)を起動しようとしたところ、クエリが実行中のままとなってしまい、

    応答が返ってこなかったからです。

     

    ちなみに、以下のバージョンでテストを行いました。

     ・SQL Server 2000(SP4)

     ・SQL Server 2005(SP2)

     ・SQL Server 2005 Express Edition

     

    2008年9月12日 9:51
  • >とありますが、これはGUIを持ったアプリケーションでしょうか? それともコンソールアプリケーションでしょうか?

    GUIフォームです。もしかして、フォーム系のモジュールを使った場合、‐1になるかな。

     

    EXE内容としては、コマンドラインで入力したパラメータを利用して、ZIPファイルの解凍・圧縮処理です。

     

    >というのも、試しにGUIを持ったアプリケーション(メモ帳)を起動しようとしたところ、クエリが実行中のままとなってしまい、

    応答が返ってこなかったからです。

    それはですね。きっとmessagebox.showやmsgboxのような関数が入っていたでしょう。

    簡単にvbsで、msgbox "ok"、この一行だけで、xp_cmdshellが永遠処理になってしまいます。

    2008年9月14日 12:32
  • こんにちは、naginoです。

     

    試しに C# で以下のコンソールアプリケーションを作成しました。

    Code Snippet
    namespace consoleTest01
    {
     class Program
     {
      static int Main(string[] args)
      {
       Console.WriteLine("test output");
       return -1;
      }
     }
    }

     

    これを、Cドライブ直下に consoleTest01.exe という名前で保存し、以下のクエリを実行しました。

    Code Snippet
    DECLARE @result nvarchar(255)
    EXEC @result = xp_cmdshell 'c:\consoleTest01.exe'
    select @result

     

    結果、EXEC文の実行結果として「test output」が、select文の実行結果として「-1」が返ってきました。

    プログラムの return の値を 2 に変えると、select文の実行結果として「2」が返ってきました。

     

    どうやらプログラムの返り値があると、そちらが返ってくるようです。

    返り値が無いコマンドを実行した場合は、Books Online の通り 0 か 1 が返ってくるようです。

     

    なお、xp_cmdshell の戻り値は、以下に記載があります。

    http://msdn.microsoft.com/ja-jp/library/ms187389.aspx

    たしかに返り値は「0 (成功) または 1 (失敗)」とあります。

     

    ご参考になれば幸いです。

     

    2008年9月15日 12:44
  •  nagino さんからの引用

    こんにちは、naginoです。

     

    試しに C# で以下のコンソールアプリケーションを作成しました。

    Code Snippet
    namespace consoleTest01
    {
     class Program
     {
      static int Main(string[] args)
      {
       Console.WriteLine("test output");
       return -1;
      }
     }
    }

     

    これを、Cドライブ直下に consoleTest01.exe という名前で保存し、以下のクエリを実行しました。

    Code Snippet
    DECLARE @result nvarchar(255)
    EXEC @result = xp_cmdshell 'c:\consoleTest01.exe'
    select @result

     

    結果、EXEC文の実行結果として「test output」が、select文の実行結果として「-1」が返ってきました。

    プログラムの return の値を 2 に変えると、select文の実行結果として「2」が返ってきました。

     

    どうやらプログラムの返り値があると、そちらが返ってくるようです。

    返り値が無いコマンドを実行した場合は、Books Online の通り 0 か 1 が返ってくるようです。

     

    なお、xp_cmdshell の戻り値は、以下に記載があります。

    http://msdn.microsoft.com/ja-jp/library/ms187389.aspx

    たしかに返り値は「0 (成功) または 1 (失敗)」とあります。

     

    ご参考になれば幸いです。

     

    確かに、コンソールアプリはそうでしたが、フォームアプリで作る場合、リターン値を設定せず、xp_cmdshellのほうが‐1になります。

    ようするに、マニュアル0と1しか書いていないですから、‐1の場合はどんなことが起こったのか、を知りたいですね。

     

    私が作ったVB2008のEXEプログラムのリターン値を設定していません。EXE処理するだけです。

    以下はすべてのソースコードではありません。一部だけです。

     

    Imports System
    Imports System.IO
    Imports System.Text
    Public Class Form1
        '***************************************
        'Add by yangjiayi 2008/07/11
        'Public変数定義
        '***************************************
        Public strCommandarg As String
        Public arrCommandarg() As String

      Public strErrMsg As String

        '***************************************
        'Add by yangjiayi 2008/07/09
        'Form1_Loadイベント
        '***************************************
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            '画面初期化
            Me.WindowState = FormWindowState.Minimized
            Me.SizeGripStyle = Windows.Forms.SizeGripStyle.Hide
            Me.Size = New Size(MinimumSize)
            Me.MaximizeBox = False
            Me.MinimizeBox = False
            Me.Opacity = 0
            Me.ShowIcon = False
            Me.ShowInTaskbar = False

     

            'コマンドラインゲット
            strCommandarg = Command()

        
            arrCommandarg = Split(strCommandarg.Replace("""", ""), ",")

            

       'メイン分枝処理
            Select Case arrCommandarg(0).ToUpper
                Case "-E"
                    '解答ファイル処理
                    If Not func_eZIP() Then
                        '強制終了
                        Process.GetCurrentProcess.Kill()
                    End If
                Case "-C"
                    '圧縮ファイル処理
                    If Not func_cZIP() Then
                        '強制終了
                        Process.GetCurrentProcess.Kill()
                    End If
            End Select

            '強制終了
            Process.GetCurrentProcess.Kill()
        End Sub

     

      '***************************************
        'Add by yangjiayi 2008/07/11
        'メインZIP解凍処理及びファイル処理
        '***************************************
        Function func_eZIP() As Boolean
            func_eZIP = False

     

            If Not func_extractZip(My.Computer.FileSystem.GetFileInfo(arrCommandarg(1)).FullName, _
                                   My.Computer.FileSystem.GetFileInfo(arrCommandarg(1)).DirectoryName & "\..\..\DAT\INPUT\", _
                                   strErrMsg) Then
                'ログファイルを書き出す
                …
                Exit Function
            Else
                '処理正常の場合
                …
            End If

           

        func_eZIP = True
        End Function

     

    '***************************************
        'Add by yangjiayi 2008/07/09
        '引数1(strFromPathfile):ZIPファイルパス
        '引数2(strToPathfolder):ZIPファイル解凍対象パス
        '引数3(strMessage):ZIP解答時のエラーメッセージ
        '***************************************
        Function func_extractZip(ByVal strFromPathfile As String, _
                                 ByVal strToPathfolder As String, _
                                 ByRef strMessage As String) As Boolean
            '初期化
            func_extractZip = False

            '展開するZIPファイルの設定
            Dim zipPath As String = strFromPathfile
            '展開先のフォルダの設定
            Dim extractDir As String = strToPathfolder

            '読み込む
            Dim fs As New System.IO.FileStream( _
                zipPath, System.IO.FileMode.Open, _
                System.IO.FileAccess.Read, System.IO.FileShare.Read)
            Dim zis As New ICSharpCode.SharpZipLib.Zip.ZipInputStream(fs)
            'パスワードが設定されているとき
            'zis.Password = "pass"

            'ZIP内のファイル情報を取得
            Dim ze As ICSharpCode.SharpZipLib.Zip.ZipEntry
            While True
                Try
                    ze = zis.GetNextEntry()
                Catch ex As Exception
                    strMessage = ex.Message
                    ze = Nothing
                End Try

                If ze Is Nothing Then
                    Exit While
                End If

                If Not ze.IsDirectory Then
                    'ファイル名
                    Dim fileName As String = System.IO.Path.GetFileName(ze.Name)
                    '展開先のフォルダ
                    Dim destDir As String = System.IO.Path.Combine( _
                        extractDir, System.IO.Path.GetDirectoryName(ze.Name))
                    System.IO.Directory.CreateDirectory(destDir)
                    '展開先のパス
                    Dim destPath As String = _
                        System.IO.Path.Combine(destDir, fileName)

                    '書込み
                    Dim writer As New System.IO.FileStream( _
                        destPath, System.IO.FileMode.Create, _
                        System.IO.FileAccess.Write, System.IO.FileShare.Write)
                    Dim buffer(2047) As Byte
                    Dim intlen As Integer
                    While True
                        Try
                            intlen = zis.Read(buffer, 0, buffer.Length)
                        Catch ex As Exception
                            strMessage = ex.Message
                            intlen = -1
                        End Try

                        If intlen <= 0 Then
                            Exit While
                        End If
                        writer.Write(buffer, 0, intlen)
                        'Return値を設定する
                        func_extractZip = True
                    End While
                    '閉じる
                    writer.Close()
                End If
            End While

            zis.Close()
            fs.Close()
        End Function

     

    もちろん、ある部分を削除しており、大体そんな感じです。

     

    以上のソースコードをコンソールアプリにしても‐1になります。

    2008年9月16日 1:02
  • アプリケーションを終了するのに Process.GetCurrentProcess.Kill() を使っているんですか?

    多分それが原因です。

     

    検証するために、Form_Loadでプログラムを終了だけのプログラムを作成し、検証してみました。

    パターン1:Process.GetCurrentProcess.Kill()で終了する。

     xp_cmdshellの戻り値は-1

     

    パターン2:Application.Exit()で終了する。

     xp_cmdshellの戻り値は0

     

    パターン3:Me.Close()
           Me.Dispose()で終了する。

     xp_cmdshellの戻り値は0

     

    おそらくは、xp_cmdshellが起動したプログラムの戻り値を受け取る前にプロセスが終了したために

    0でも1でもなく、(意図としては起動したプロセスが異常終了したというエラーとして)-1を返したものと思われます。

     

    # 老婆心ですが、Process.GetCurrentProcess.Kill() はマルチスレッドで他スレッドを含めて強制終了する場合以外、

    # 使わない方がいいような・・・。

    # あとxp_cmdshellもセキュリティ上、あまり使わない方がいいような。

    # (まあ、設定次第ですけど)

    2008年9月16日 3:02
  •  CatTail さんからの引用

    アプリケーションを終了するのに Process.GetCurrentProcess.Kill() を使っているんですか?

    多分それが原因です。

     

    検証するために、Form_Loadでプログラムを終了だけのプログラムを作成し、検証してみました。

    パターン1:Process.GetCurrentProcess.Kill()で終了する。

     xp_cmdshellの戻り値は-1

     

    パターン2:Application.Exit()で終了する。

     xp_cmdshellの戻り値は0

     

    パターン3:Me.Close()
           Me.Dispose()で終了する。

     xp_cmdshellの戻り値は0

     

    おそらくは、xp_cmdshellが起動したプログラムの戻り値を受け取る前にプロセスが終了したために

    0でも1でもなく、(意図としては起動したプロセスが異常終了したというエラーとして)-1を返したものと思われます。

     

    # 老婆心ですが、Process.GetCurrentProcess.Kill() はマルチスレッドで他スレッドを含めて強制終了する場合以外、

    # 使わない方がいいような・・・。

    # あとxp_cmdshellもセキュリティ上、あまり使わない方がいいような。

    # (まあ、設定次第ですけど)

    なんか私もProcess.GetCurrentProcess.Kill()のほうが悪かったっぽいと思いましたね。VBのほうを修正します。

    Me.Close()
    とか使った場合、タスクマネージャにプロセスが残ったままの場合が多いです。なぜでしょうか。

     

    一応、

    以下のプロシージャのように、SYSADMINではないユーザでも実行できるようにしています。

     

    参考:

     

    USE [DB]
    GO

    /****** Object:  StoredProcedure [dbo].[nsp_CheckUserAuthority]    Script Date: 09/17/2008 09:24:55 ******/
    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[nsp_CheckUserAuthority]') AND type in (N'P', N'PC'))
    DROP PROCEDURE [dbo].[nsp_CheckUserAuthority]
    GO

    USE [DB]
    GO

    /****** Object:  StoredProcedure [dbo].[nsp_CheckUserAuthority]    Script Date: 09/17/2008 09:24:55 ******/
    SET ANSI_NULLS ON
    GO

    SET QUOTED_IDENTIFIER ON
    GO

    -- =============================================
    -- Author:  <yangjiayi>
    -- Create date: <2008/09/12>
    -- Description: <Userのログイン権限チェック>
    -- =============================================
    CREATE PROCEDURE [dbo].[nsp_CheckUserAuthority]
     @Path NVARCHAR(4000) = ''
    AS
     BEGIN
      SET NOCOUNT ON
      DECLARE @Command NVARCHAR(4000)
      
      IF (NOT IS_SRVROLEMEMBER('sysadmin') = 1)
       BEGIN
        SET @Command =
        'SELECT a.[Name] '+
        'FROM '+
        'OPENROWSET(''SQLOLEDB'', ''' + @@SERVERNAME + '''; ''sa''; ''password'', ' +
        ' ''EXEC DB.dbo.nsp_RunXpCmdShell ''' +
        '''' + REPLACE(@Path, '''', '''''') + '''' +
        ''' '') AS a '
        EXEC sp_executesql @Command
       END
      ELSE
       BEGIN
        EXEC DB.dbo.nsp_RunXpCmdShell @Path
       END
      RETURN
     END


    GO

    ************************************************

    USE [DB]
    GO

    /****** Object:  StoredProcedure [dbo].[nsp_RunXpCmdShell]    Script Date: 09/17/2008 09:25:30 ******/
    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[nsp_RunXpCmdShell]') AND type in (N'P', N'PC'))
    DROP PROCEDURE [dbo].[nsp_RunXpCmdShell]
    GO

    USE [DB]
    GO

    /****** Object:  StoredProcedure [dbo].[nsp_RunXpCmdShell]    Script Date: 09/17/2008 09:25:30 ******/
    SET ANSI_NULLS ON
    GO

    SET QUOTED_IDENTIFIER ON
    GO

    -- =============================================
    -- Author:  <yangjiayi>
    -- Create date: <2008/09/12>
    -- Description: <Run xp_cmdshell>
    -- =============================================
    CREATE PROCEDURE [dbo].[nsp_RunXpCmdShell]
     @Path NVARCHAR(4000) = ''
    AS
    BEGIN
     SET NOCOUNT ON
     DECLARE @R INT
     DECLARE @Command NVARCHAR(4000)
     DECLARE @Name NVARCHAR(max)

     -- sysadmin かどうか確認するログイン
     IF (NOT IS_SRVROLEMEMBER('sysadmin') = 1)
      BEGIN
       SET @Name = 'Error!sysadminではありません。(nsp_RunXpCmdShell)'
       SELECT @Name AS [Name]
       RETURN -1
      END

     -- @Path の中身を検査
     IF CHARINDEX(N'..', @Path) > 0 OR
     CHARINDEX(N'"', @Path) > 0 OR
     CHARINDEX(N'&', @Path) > 0 OR
     CHARINDEX(N'|', @Path) > 0
      BEGIN
       SET @Name = N'Error!引数に不適切な文字列が入っています。(nsp_RunXpCmdShell)'
       SELECT @Name AS [Name]
       RETURN -1
      END

     CREATE TABLE #Files
     (
      [Name] NVARCHAR(max)
     )

     SET @Command = @Path

     INSERT INTO #Files
     EXEC @R = master.dbo.xp_cmdshell @Command
     
     IF @R = 0
      BEGIN
       SELECT Name AS [Name] FROM #Files WHERE Name IS NOT NULL
      END
     
     IF @R = -1
      BEGIN
       SET @Name = CONVERT(NVARCHAR,@R)
       SELECT @Name AS [Name]
      END
     
     IF @R = 1
      BEGIN
       SET @Name = N'Error!xp_cmdshell実行時エラーが発生しました。(nsp_RunXpCmdShell)'
       SELECT @Name AS [Name]
       RETURN -1
      END

     DROP TABLE #Files
    END

    GO

     

    2008年9月17日 0:29
  • 解決済みとなったスレにレスするのもアレですが、

     

    > Me.Close()
    > とか使った場合、タスクマネージャにプロセスが残ったままの場合が多いです。なぜでしょうか。

     

    例えば、メインフォームからサブフォームを開き、サブフォームを閉じただけでサブフォームのインスタンスを

    破棄しないと(サブフォームの)インスタンスが残ったままとなってしない、メインフォームを閉じても

    プロセスは残ったままとなったはずです。

     

    SDIであればApplication.Exit()を使うとか、Form_Closingで後処理するとかという方法が考えられますが、

    この辺に関しては余り詳しくないので、これに関してはVBフォーラムで質問すると妥当な回答が帰ってくると思います。

     

    2008年9月17日 13:45