How do I throw custom errors from SQLCLR code back to calling T-SQL?

  • I have a SQL CLR sproc that lists the contents of a folder.

    It is called from the T-SQL code like this:

    BEGIN TRY

    EXEC usp_ListFolder @path = N'blahblahblah';

    END TRY

    BEGIN CATCH

    PRINT ERROR_MESSAGE()

    END CATCH

    Output:

    A .NET Framework error occurred during execution of user-defined routine or aggregate "usp_ListFolder":

    System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\WINDOWS\system32\blahblahblah'.

    System.IO.DirectoryNotFoundException:

    at FileIO.SendError(Exception e)

    at FileIO.ListFolder(String directory)

    .

    I would like to change the C# code to emit a more user-friendly error message, such as:

    System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\WINDOWS\system32\blahblahblah'.

    Right now, my C# code looks like this:

    private static void SendError( Exception e )

    {

    throw (e);

    }

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • You can't change how it outputs the errors, but you can change how you consume them. For example, you can create a simple function to clean it up like:

    [Microsoft.SqlServer.Server.SqlFunction]

    public static SqlString CleanCLRError(SqlString errorMsg)

    {

    return new SqlString(errorMsg.ToString().Split("\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1]);

    }

    You then use it in your catch block like this:

    declare @err varchar(max)

    begin try

    exec GenerateError

    end try

    begin catch

    print dbo.CleanCLRError(error_message())

    end catch

    The TSQL Equivalent to it is:

    create function CleanCLRError_TSQL

    (@errorMsg varchar(max))

    returns varchar(max)

    as

    begin

    return (left(substring(@errorMsg, patindex('%'+char(10)+'%', @errorMsg), len(@errorMsg)), patindex('%'+char(10)+'%', substring(@errorMsg, patindex('%'+char(10)+'%', @errorMsg)+1, len(@errorMsg)))))

    end

    Hope this helps.

    Jonathan Kehayias | Principal Consultant | MCM: SQL Server 2008
    My Blog | Twitter | MVP Profile
    Training | Consulting | Become a SQLskills Insider
    Troubleshooting SQL Server: A Guide for Accidental DBAs[/url]

  • Thank you, I will try that.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Just a note,

    If I had to implement this, I would go the TSQL Route for the function over paying the cost of the CLR function in loading an AppDomain, and calling out to the CLR Stack. It just makes more sense. It is deterministic, and who cares that the code for it is really nasty, you are calling a function, so you don't have to remember the code anyway.

    Jonathan Kehayias | Principal Consultant | MCM: SQL Server 2008
    My Blog | Twitter | MVP Profile
    Training | Consulting | Become a SQLskills Insider
    Troubleshooting SQL Server: A Guide for Accidental DBAs[/url]

  • Marios Philippopoulos (5/30/2008)


    I have a SQL CLR sproc that lists the contents of a folder.

    Just curious... what columns are you returning fo this?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (7/6/2008)


    Marios Philippopoulos (5/30/2008)


    I have a SQL CLR sproc that lists the contents of a folder.

    Just curious... what columns are you returning fo this?

    Here is some sample output:

    Date Type Size Name

    ----------------------- ---- -------------------- ------------------------

    2006-08-17 16:27:44.110 F 431 $winnt$.inf

    2003-03-25 08:00:00.000 F 2151 12520437.cpx

    2003-03-25 08:00:00.000 F 2233 12520850.cpx

    2006-08-16 09:52:05.000 F 99840 6to4svc.dll

    2005-03-24 20:55:26.000 F 35328 aaaamon.dll

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • I hate to say it - but you CAN change the errors if you so desire. Just don't have the CLR code "throw" the error again: instead have it create the happy error message. Let the T-SQL code raise an error if you so desire once it has the return message in hand.

    Just have the CLR procedure/function catch the error, and pass the "friendlier" message back as an output parameter. Set your return code so that you can figure out that it wasn't a successful run....

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Thanks,

    Just a couple more questions...

    1) Which date are you listing... Create, Modified, or Last Accessed?

    2) Are you allowed to use sp_OA* procedures?

    3) Do you really need the date and the size?

    The reason for all the questions is, obviously, there's an all T-SQL solution

    to totally replace the need for a CLR... I just want to make sure to give you

    a solution that actually does what you want it to....

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Matt Miller (7/7/2008)


    I hate to say it - but you CAN change the errors if you so desire. Just don't have the CLR code "throw" the error again: instead have it create the happy error message. Let the T-SQL code raise an error if you so desire once it has the return message in hand.

    Just have the CLR procedure/function catch the error, and pass the "friendlier" message back as an output parameter. Set your return code so that you can figure out that it wasn't a successful run....

    The developers wanted to use TRY/CATCH logic to catch the errors, so I had to accommodate.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Jeff Moden (7/7/2008)


    Thanks,

    Just a couple more questions...

    1) Which date are you listing... Create, Modified, or Last Accessed?

    2) Are you allowed to use sp_OA* procedures?

    3) Do you really need the date and the size?

    The reason for all the questions is, obviously, there's an all T-SQL solution

    to totally replace the need for a CLR... I just want to make sure to give you

    a solution that actually does what you want it to....

    1) Which date are you listing... Create, Modified, or Last Accessed?

    The date is the Last Write Time.

    2) Are you allowed to use sp_OA* procedures?

    I don't know much about sp_OA* procedures, but I think they come with their own baggage of problems, and in any case, I don't think they are nearly as customizable and extensible as the SQLCLR solution.

    3) Do you really need the date and the size?

    The code is used primarily by other users who might need to use these and other columns as well, that I have not included in the returned resultset as yet.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Marios Philippopoulos (7/7/2008)


    Matt Miller (7/7/2008)


    I hate to say it - but you CAN change the errors if you so desire. Just don't have the CLR code "throw" the error again: instead have it create the happy error message. Let the T-SQL code raise an error if you so desire once it has the return message in hand.

    Just have the CLR procedure/function catch the error, and pass the "friendlier" message back as an output parameter. Set your return code so that you can figure out that it wasn't a successful run....

    The developers wanted to use TRY/CATCH logic to catch the errors, so I had to accommodate.

    Fair enough.

    I would then use Jonathan's idea, except that the "clean" function would intercept and change the error message altogether, likely based on the error number, and not the message. Build a custom errors table to pass whatever message you wish, and simply lookup the error number there.

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Marios Philippopoulos (7/7/2008)


    2) Are you allowed to use sp_OA* procedures?

    I don't know much about sp_OA* procedures, but I think they come with their own baggage of problems, and in any case, I don't think they are nearly as customizable and extensible as the SQLCLR solution.

    Heh... how can you say that? You just said you don't know much about them. 😉

    Alright... check this out...

    -- CREATE PROCEDURE dbo.GetFileDetails

    -- --===== Declare the I/O parameters

    -- @piFullPath VARCHAR(128)

    -- AS

    declare @piFullPath VARCHAR(128)

    set @piFullPath = 'c:\temp\'

    SET NOCOUNT ON

    --=======================================================================================

    -- Local variables

    --=======================================================================================

    --===== These are processing control and reporting variables

    DECLARE @Counter INT --General purpose counter

    DECLARE @CurrentName VARCHAR(256) --Name of file currently being worked

    DECLARE @DirTreeCount INT --Remembers number of rows for xp_DirTree

    DECLARE @ErrorMessage VARCHAR(255) --the potential error message

    DECLARE @ErrorObject INT --the error object

    DECLARE @HR INT --the HRESULT returned from

    DECLARE @IsFile BIT --1 if Name is a file, 0 if not

    --===== The are object "handle" variables

    DECLARE @ObjFile INT --File object

    DECLARE @ObjFileSystem INT --File System Object

    --===== These variable names match the sp_OAGetProperty options

    -- Made names match so they're less confusing

    DECLARE @Attributes INT --Read only, Hidden, Archived, etc

    DECLARE @DateCreated DATETIME --Date file was created

    DECLARE @DateLastAccessed DATETIME --Date file was last read

    DECLARE @DateLastModified DATETIME --Date file was last written to

    DECLARE @Name VARCHAR(128) --File Name and Extension

    DECLARE @Path VARCHAR(128) --Full path including file name

    DECLARE @ShortName VARCHAR(12) --8.3 file name

    DECLARE @ShortPath VARCHAR(100) --8.3 full path including file name

    DECLARE @Size INT --File size in bytes

    DECLARE @Type VARCHAR(100) --Long Windows file type (eg.'Text Document',etc)

    --=======================================================================================

    -- Create temporary working tables

    --=======================================================================================

    --===== Create a place to store all file names derived from xp_DirTree

    IF OBJECT_ID('TempDB..#DirTree','U') IS NOT NULL

    DROP TABLE #DirTree

    CREATE TABLE #DirTree

    (

    RowNum INT IDENTITY(1,1),

    Name VARCHAR(256) PRIMARY KEY CLUSTERED,

    Depth BIT,

    IsFile BIT)

    --===== Create a place to store the file details

    IF OBJECT_ID('TempDB..#FileDetails','U') IS NOT NULL

    DROP TABLE #FileDetails

    CREATE TABLE #FileDetails

    (

    RowNum INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,

    Name VARCHAR(128), --File Name and Extension

    Path VARCHAR(128), --Full path including file name

    ShortName VARCHAR(12), --8.3 file name

    ShortPath VARCHAR(100), --8.3 full path including file name

    DateCreated DATETIME, --Date file was created

    DateLastAccessed DATETIME, --Date file was last read

    DateLastModified DATETIME, --Date file was last written to

    Attributes INT, --Read only, Hidden, Archived, etc

    Size INT, --File size in bytes

    Type VARCHAR(100)) --Long Windows file type (eg.'Text Document',etc)

    --=======================================================================================

    -- Get all the file names for the directory (includes directory names as IsFile = 0)

    --=======================================================================================

    --===== Get the file names for the desired path

    INSERT INTO #DirTree (Name, Depth, IsFile)

    EXEC Master.dbo.xp_Dirtree @piFullPath,1,1 -- Current diretory only, list file names

    -- Remember the row count

    SET @DirTreeCount = @@ROWCOUNT

    --===== Update the file names with the path for ease of processing later on

    UPDATE #DirTree

    SET Name = @piFullPath + Name

    --select * from #dirtree

    --=======================================================================================

    --

    --=======================================================================================

    --===== Create a file system object and remember the "handle"

    EXEC @HR = dbo.sp_OACreate 'Scripting.FileSystemObject', @ObjFileSystem OUT

    SET @Counter = 1

    WHILE @Counter <= @DirTreeCount

    BEGIN

    --===== Get the current name and see if it's a file

    SELECT @CurrentName = Name,

    @IsFile = IsFile

    FROM #DirTree

    WHERE RowNum = @Counter

    --===== If it's a file, get the details for it

    IF @IsFile = 1 AND @CurrentName LIKE '%%'

    BEGIN

    --===== Create an object for the path/file and remember the "handle"

    EXEC dbo.sp_OAMethod @ObjFileSystem,'GetFile', @ObjFile OUT, @CurrentName

    --===== Get the all the required attributes for the file itself

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Path', @Path OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortPath', @ShortPath OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Name', @Name OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortName', @ShortName OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateCreated', @DateCreated OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastAccessed', @DateLastAccessed OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastModified', @DateLastModified OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Attributes', @Attributes OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Size', @Size OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Type', @Type OUT

    INSERT INTO #FileDetails

    (Path, ShortPath, Name, ShortName, DateCreated, DateLastAccessed, DateLastModified, Attributes, Size, Type)

    SELECT @Path,@ShortPath,@Name,@ShortName,@DateCreated,@DateLastAccessed,@DateLastModified,@Attributes,@Size,@Type

    END

    SELECT @Counter = @Counter + 1

    END

    EXEC sp_OADestroy @ObjFileSystem

    EXEC sp_OADestroy @ObjFile

    SELECT * FROM #FileDetails

    Do ya suppose that's just as "customizable and extensible" as a CLR for the same thing?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Matt Miller (7/7/2008)


    Marios Philippopoulos (7/7/2008)


    Matt Miller (7/7/2008)


    I hate to say it - but you CAN change the errors if you so desire. Just don't have the CLR code "throw" the error again: instead have it create the happy error message. Let the T-SQL code raise an error if you so desire once it has the return message in hand.

    Just have the CLR procedure/function catch the error, and pass the "friendlier" message back as an output parameter. Set your return code so that you can figure out that it wasn't a successful run....

    The developers wanted to use TRY/CATCH logic to catch the errors, so I had to accommodate.

    Fair enough.

    I would then use Jonathan's idea, except that the "clean" function would intercept and change the error message altogether, likely based on the error number, and not the message. Build a custom errors table to pass whatever message you wish, and simply lookup the error number there.

    Yeah, I like the lookup table idea, thanks!

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Jeff Moden (7/7/2008)


    Marios Philippopoulos (7/7/2008)


    2) Are you allowed to use sp_OA* procedures?

    I don't know much about sp_OA* procedures, but I think they come with their own baggage of problems, and in any case, I don't think they are nearly as customizable and extensible as the SQLCLR solution.

    Heh... how can you say that? You just said you don't know much about them. 😉

    Alright... check this out...

    -- CREATE PROCEDURE dbo.GetFileDetails

    -- --===== Declare the I/O parameters

    -- @piFullPath VARCHAR(128)

    -- AS

    declare @piFullPath VARCHAR(128)

    set @piFullPath = 'c:\temp\'

    SET NOCOUNT ON

    --=======================================================================================

    -- Local variables

    --=======================================================================================

    --===== These are processing control and reporting variables

    DECLARE @Counter INT --General purpose counter

    DECLARE @CurrentName VARCHAR(256) --Name of file currently being worked

    DECLARE @DirTreeCount INT --Remembers number of rows for xp_DirTree

    DECLARE @ErrorMessage VARCHAR(255) --the potential error message

    DECLARE @ErrorObject INT --the error object

    DECLARE @HR INT --the HRESULT returned from

    DECLARE @IsFile BIT --1 if Name is a file, 0 if not

    --===== The are object "handle" variables

    DECLARE @ObjFile INT --File object

    DECLARE @ObjFileSystem INT --File System Object

    --===== These variable names match the sp_OAGetProperty options

    -- Made names match so they're less confusing

    DECLARE @Attributes INT --Read only, Hidden, Archived, etc

    DECLARE @DateCreated DATETIME --Date file was created

    DECLARE @DateLastAccessed DATETIME --Date file was last read

    DECLARE @DateLastModified DATETIME --Date file was last written to

    DECLARE @Name VARCHAR(128) --File Name and Extension

    DECLARE @Path VARCHAR(128) --Full path including file name

    DECLARE @ShortName VARCHAR(12) --8.3 file name

    DECLARE @ShortPath VARCHAR(100) --8.3 full path including file name

    DECLARE @Size INT --File size in bytes

    DECLARE @Type VARCHAR(100) --Long Windows file type (eg.'Text Document',etc)

    --=======================================================================================

    -- Create temporary working tables

    --=======================================================================================

    --===== Create a place to store all file names derived from xp_DirTree

    IF OBJECT_ID('TempDB..#DirTree','U') IS NOT NULL

    DROP TABLE #DirTree

    CREATE TABLE #DirTree

    (

    RowNum INT IDENTITY(1,1),

    Name VARCHAR(256) PRIMARY KEY CLUSTERED,

    Depth BIT,

    IsFile BIT)

    --===== Create a place to store the file details

    IF OBJECT_ID('TempDB..#FileDetails','U') IS NOT NULL

    DROP TABLE #FileDetails

    CREATE TABLE #FileDetails

    (

    RowNum INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,

    Name VARCHAR(128), --File Name and Extension

    Path VARCHAR(128), --Full path including file name

    ShortName VARCHAR(12), --8.3 file name

    ShortPath VARCHAR(100), --8.3 full path including file name

    DateCreated DATETIME, --Date file was created

    DateLastAccessed DATETIME, --Date file was last read

    DateLastModified DATETIME, --Date file was last written to

    Attributes INT, --Read only, Hidden, Archived, etc

    Size INT, --File size in bytes

    Type VARCHAR(100) --Long Windows file type (eg.'Text Document',etc))

    --=======================================================================================

    -- Get all the file names for the directory (includes directory names as IsFile = 0)

    --=======================================================================================

    --===== Get the file names for the desired path

    INSERT INTO #DirTree (Name, Depth, IsFile)

    EXEC Master.dbo.xp_Dirtree @piFullPath,1,1 -- Current diretory only, list file names

    -- Remember the row count

    SET @DirTreeCount = @@ROWCOUNT

    --===== Update the file names with the path for ease of processing later on

    UPDATE #DirTree

    SET Name = @piFullPath + Name

    --select * from #dirtree

    --=======================================================================================

    --

    --=======================================================================================

    --===== Create a file system object and remember the "handle"

    EXEC @HR = dbo.sp_OACreate 'Scripting.FileSystemObject', @ObjFileSystem OUT

    SET @Counter = 1

    WHILE @Counter <= @DirTreeCount

    BEGIN

    --===== Get the current name and see if it's a file

    SELECT @CurrentName = Name,

    @IsFile = IsFile

    FROM #DirTree

    WHERE RowNum = @Counter

    --===== If it's a file, get the details for it

    IF @IsFile = 1 AND @CurrentName LIKE '%%'

    BEGIN

    --===== Create an object for the path/file and remember the "handle"

    EXEC dbo.sp_OAMethod @ObjFileSystem,'GetFile', @ObjFile OUT, @CurrentName

    --===== Get the all the required attributes for the file itself

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Path', @Path OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortPath', @ShortPath OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Name', @Name OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortName', @ShortName OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateCreated', @DateCreated OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastAccessed', @DateLastAccessed OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastModified', @DateLastModified OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Attributes', @Attributes OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Size', @Size OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Type', @Type OUT

    INSERT INTO #FileDetails

    (Path, ShortPath, Name, ShortName, DateCreated, DateLastAccessed, DateLastModified, Attributes, Size, Type)

    SELECT @Path,@ShortPath,@Name,@ShortName,@DateCreated,@DateLastAccessed,@DateLastModified,@Attributes,@Size,@Type

    END

    SELECT @Counter = @Counter + 1

    END

    EXEC sp_OADestroy @ObjFileSystem

    EXEC sp_OADestroy @ObjFile

    SELECT * FROM #FileDetails

    Do ya suppose that's just as "customizable and extensible" as a CLR for the same thing?

    OK, maybe I'm a little biased, but pls take a look at the following link by Adam Machanic on the pros and cons of SQLCLR vs. sp_OA* type functionality:

    http://www.eggheadcafe.com/software/aspnet/30214874/clr-vs-spoacreate.aspx

    CLR vs. spOACreate - Adam Machanic

    21-Jun-07 02:16:09

    Aside from the MUCH nicer programming model, SQLCLR routines will tend to

    perform and scale better, and you don't have the potential for memory leaks

    and other nasty side-effects that you do with sp_OA*. If you have something

    already working and it's fairly complex, it may be hard to argue in favor of

    a re-write; but perhaps next time you go to modify the functionality you

    should think about it.

    __________________________________________________________________________________
    SQL Server 2016 Columnstore Index Enhancements - System Views for Disk-Based Tables[/url]
    Persisting SQL Server Index-Usage Statistics with MERGE[/url]
    Turbocharge Your Database Maintenance With Service Broker: Part 2[/url]

  • Jeff Moden (7/7/2008)


    Marios Philippopoulos (7/7/2008)


    2) Are you allowed to use sp_OA* procedures?

    I don't know much about sp_OA* procedures, but I think they come with their own baggage of problems, and in any case, I don't think they are nearly as customizable and extensible as the SQLCLR solution.

    Heh... how can you say that? You just said you don't know much about them. 😉

    Alright... check this out...

    -- CREATE PROCEDURE dbo.GetFileDetails

    -- --===== Declare the I/O parameters

    -- @piFullPath VARCHAR(128)

    -- AS

    declare @piFullPath VARCHAR(128)

    set @piFullPath = 'c:\temp\'

    SET NOCOUNT ON

    --=======================================================================================

    -- Local variables

    --=======================================================================================

    --===== These are processing control and reporting variables

    DECLARE @Counter INT --General purpose counter

    DECLARE @CurrentName VARCHAR(256) --Name of file currently being worked

    DECLARE @DirTreeCount INT --Remembers number of rows for xp_DirTree

    DECLARE @ErrorMessage VARCHAR(255) --the potential error message

    DECLARE @ErrorObject INT --the error object

    DECLARE @HR INT --the HRESULT returned from

    DECLARE @IsFile BIT --1 if Name is a file, 0 if not

    --===== The are object "handle" variables

    DECLARE @ObjFile INT --File object

    DECLARE @ObjFileSystem INT --File System Object

    --===== These variable names match the sp_OAGetProperty options

    -- Made names match so they're less confusing

    DECLARE @Attributes INT --Read only, Hidden, Archived, etc

    DECLARE @DateCreated DATETIME --Date file was created

    DECLARE @DateLastAccessed DATETIME --Date file was last read

    DECLARE @DateLastModified DATETIME --Date file was last written to

    DECLARE @Name VARCHAR(128) --File Name and Extension

    DECLARE @Path VARCHAR(128) --Full path including file name

    DECLARE @ShortName VARCHAR(12) --8.3 file name

    DECLARE @ShortPath VARCHAR(100) --8.3 full path including file name

    DECLARE @Size INT --File size in bytes

    DECLARE @Type VARCHAR(100) --Long Windows file type (eg.'Text Document',etc)

    --=======================================================================================

    -- Create temporary working tables

    --=======================================================================================

    --===== Create a place to store all file names derived from xp_DirTree

    IF OBJECT_ID('TempDB..#DirTree','U') IS NOT NULL

    DROP TABLE #DirTree

    CREATE TABLE #DirTree

    (

    RowNum INT IDENTITY(1,1),

    Name VARCHAR(256) PRIMARY KEY CLUSTERED,

    Depth BIT,

    IsFile BIT)

    --===== Create a place to store the file details

    IF OBJECT_ID('TempDB..#FileDetails','U') IS NOT NULL

    DROP TABLE #FileDetails

    CREATE TABLE #FileDetails

    (

    RowNum INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,

    Name VARCHAR(128), --File Name and Extension

    Path VARCHAR(128), --Full path including file name

    ShortName VARCHAR(12), --8.3 file name

    ShortPath VARCHAR(100), --8.3 full path including file name

    DateCreated DATETIME, --Date file was created

    DateLastAccessed DATETIME, --Date file was last read

    DateLastModified DATETIME, --Date file was last written to

    Attributes INT, --Read only, Hidden, Archived, etc

    Size INT, --File size in bytes

    Type VARCHAR(100) --Long Windows file type (eg.'Text Document',etc))

    --=======================================================================================

    -- Get all the file names for the directory (includes directory names as IsFile = 0)

    --=======================================================================================

    --===== Get the file names for the desired path

    INSERT INTO #DirTree (Name, Depth, IsFile)

    EXEC Master.dbo.xp_Dirtree @piFullPath,1,1 -- Current diretory only, list file names

    -- Remember the row count

    SET @DirTreeCount = @@ROWCOUNT

    --===== Update the file names with the path for ease of processing later on

    UPDATE #DirTree

    SET Name = @piFullPath + Name

    --select * from #dirtree

    --=======================================================================================

    --

    --=======================================================================================

    --===== Create a file system object and remember the "handle"

    EXEC @HR = dbo.sp_OACreate 'Scripting.FileSystemObject', @ObjFileSystem OUT

    SET @Counter = 1

    WHILE @Counter <= @DirTreeCount

    BEGIN

    --===== Get the current name and see if it's a file

    SELECT @CurrentName = Name,

    @IsFile = IsFile

    FROM #DirTree

    WHERE RowNum = @Counter

    --===== If it's a file, get the details for it

    IF @IsFile = 1 AND @CurrentName LIKE '%%'

    BEGIN

    --===== Create an object for the path/file and remember the "handle"

    EXEC dbo.sp_OAMethod @ObjFileSystem,'GetFile', @ObjFile OUT, @CurrentName

    --===== Get the all the required attributes for the file itself

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Path', @Path OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortPath', @ShortPath OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Name', @Name OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'ShortName', @ShortName OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateCreated', @DateCreated OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastAccessed', @DateLastAccessed OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'DateLastModified', @DateLastModified OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Attributes', @Attributes OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Size', @Size OUT

    EXEC dbo.sp_OAGetProperty @ObjFile, 'Type', @Type OUT

    INSERT INTO #FileDetails

    (Path, ShortPath, Name, ShortName, DateCreated, DateLastAccessed, DateLastModified, Attributes, Size, Type)

    SELECT @Path,@ShortPath,@Name,@ShortName,@DateCreated,@DateLastAccessed,@DateLastModified,@Attributes,@Size,@Type

    END

    SELECT @Counter = @Counter + 1

    END

    EXEC sp_OADestroy @ObjFileSystem

    EXEC sp_OADestroy @ObjFile

    SELECT * FROM #FileDetails

    Do ya suppose that's just as "customizable and extensible" as a CLR for the same thing?

    Jeff,

    Just one little problem with your code above. The closing paren for the CREATE TABLE #FileDetails is included in your comment. It took me a bit to figure out that problem, kept getting an error near the INSERT statment following it.

    😎

Viewing 15 posts - 1 through 15 (of 18 total)

You must be logged in to reply to this topic. Login to reply