tags:

views:

553

answers:

3

I've create a basic C# class that implements Microsoft.Data.Schema.ScriptDom and Microsoft.Data.Schema.ScriptDom.Sql interface. These two assemblies are part of Visual Studio Database Edition (VSDB) and are the parsing/scripting API's. You can parse SQL text and output a format SQL script. For more information about VSDB assemblies, see this blog post . Since they are redistributable, I've included both assemblies and the PowerShell script here:

#requires -version 2

add-type -path .\Microsoft.Data.Schema.ScriptDom.dll
add-type -path .\Microsoft.Data.Schema.ScriptDom.Sql.dll

$Source = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;
using System.IO;

    public class SQLParser
    {
        private IScriptFragment fragment;

        public SQLParser(SqlVersion sqlVersion, bool quotedIdentifier, string inputScript)
        {
            switch (sqlVersion)
            {
                case SqlVersion.Sql80:
                    SQLParser80 (quotedIdentifier, inputScript);
                    break;
                case SqlVersion.Sql90:
                    SQLParser90 (quotedIdentifier, inputScript);
                    break;
                case SqlVersion.Sql100:
                    SQLParser100 (quotedIdentifier, inputScript);
                    break;
            }
        }

        private void SQLParser100 (bool quotedIdentifier, string inputScript)
        {
            TSql100Parser parser = new TSql100Parser(quotedIdentifier);
            Parse(parser, inputScript);
        }

        private void SQLParser90 (bool quotedIdentifier, string inputScript)
        {
            TSql90Parser parser90 = new TSql90Parser(quotedIdentifier);
            Parse(parser90, inputScript);
        }

        private void SQLParser80 (bool quotedIdentifier, string inputScript)
        {
            TSql80Parser parser80 = new TSql80Parser(quotedIdentifier);
            Parse(parser80, inputScript);
        }

        private void Parse(TSql100Parser parser, string inputScript)
        {
            IList<ParseError> errors;

            using (StringReader sr = new StringReader(inputScript))
            {
                fragment = parser.Parse(sr, out errors);
            }

            if (errors != null && errors.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var error in errors)
                {
                    sb.AppendLine(error.Message);
                    sb.AppendLine("offset " + error.Offset.ToString());
                }
                throw new ArgumentException("InvalidSQLScript", sb.ToString());
            }
        }

        private void Parse(TSql90Parser parser, string inputScript)
        {
            IList<ParseError> errors;

            using (StringReader sr = new StringReader(inputScript))
            {
                fragment = parser.Parse(sr, out errors);
            }

            if (errors != null && errors.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var error in errors)
                {
                    sb.AppendLine(error.Message);
                    sb.AppendLine("offset " + error.Offset.ToString());
                }
                throw new ArgumentException("InvalidSQLScript", sb.ToString());
            }
        }

        private void Parse(TSql80Parser parser, string inputScript)
        {
            IList<ParseError> errors;

            using (StringReader sr = new StringReader(inputScript))
            {
                fragment = parser.Parse(sr, out errors);
            }

            if (errors != null && errors.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var error in errors)
                {
                    sb.AppendLine(error.Message);
                    sb.AppendLine("offset " + error.Offset.ToString());
                }
                throw new ArgumentException("InvalidSQLScript", sb.ToString());
            }
        }

        public IScriptFragment Fragment
        {
            get { return fragment; }
        }


    }
"@
$refs = @("Microsoft.Data.Schema.ScriptDom","Microsoft.Data.Schema.ScriptDom.Sql")
add-type -ReferencedAssemblies $refs -TypeDefinition $Source -Language CSharpVersion3 -passThru

I'm using PowerShell V2 add-type to create a runtime type. I've tested the script on 3 different machines. On one machine the script works as expected on the other two machines the following error is produced. Both referenced assemblies are placed in the same folder as the PowerShell script. Any ideas on what I'm doing wrong?

PS C:\Users\u00\bin> .\SQLParser.ps1
Add-Type : (0) : Metadata file 'Microsoft.Data.Schema.ScriptDom.dll' could not be found
(1) : using System;
At C:\Users\u00\bin\SQLParser.ps1:125 char:9
+ add-type <<<<  -ReferencedAssemblies $refs -TypeDefinition $Source -Language CSharpVersion3 -passThru
    + CategoryInfo          : InvalidData: (error CS0006: M...ld not be found:CompilerError) [Add-Type], Exception
    + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Microsoft.PowerShell.Commands.AddTypeCommand

Add-Type : (0) : Metadata file 'Microsoft.Data.Schema.ScriptDom.Sql.dll' could not be found
(1) : using System;
At C:\Users\u00\bin\SQLParser.ps1:125 char:9
+ add-type <<<<  -ReferencedAssemblies $refs -TypeDefinition $Source -Language CSharpVersion3 -passThru
    + CategoryInfo          : InvalidData: (error CS0006: M...ld not be found:CompilerError) [Add-Type], Exception
    + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Microsoft.PowerShell.Commands.AddTypeCommand

Add-Type : Cannot add type. There were compilation errors.
At C:\Users\u00\bin\SQLParser.ps1:125 char:9
+ add-type <<<<  -ReferencedAssemblies $refs -TypeDefinition $Source -Language CSharpVersion3 -passThru
    + CategoryInfo          : InvalidData: (:) [Add-Type], InvalidOperationException
    + FullyQualifiedErrorId : COMPILER_ERRORS,Microsoft.PowerShell.Commands.AddTypeCommand
+2  A: 

If you put the VSTSDB assemblies in the same dir as the script then you don't want to use "." in a relative path. "." will be relative to the directory where the script is invoked. Try something like this instead:

$ScriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
Add-Type -Path "$ScriptDir\Microsoft.Data.Schema.ScriptDom.dll"
Keith Hill
Good suggestion, but I tried that. I went ahead and tried again, Same error after making change:$ScriptDir = Split-Path ($MyInvocation.MyCommand.Path) -ParentAdd-Type -Path "$ScriptDir\Microsoft.Data.Schema.ScriptDom.dll"Add-Type -Path "$ScriptDir\Microsoft.Data.Schema.ScriptDom.Sql.dll"The assemblies are in my current directory:PS C:\Users\u00\bin> ls Microsoft.Data.Schema.ScriptDom* | select nameName----Microsoft.Data.Schema.ScriptDom.dllMicrosoft.Data.Schema.ScriptDom.Sql.dllAre you able to compile the code?
Chad Miller
Nah, I don't have the Data Dude edition of VS. Another thought, could it be that there is another assembly that is required (a dependency) that is causing the failure to load? You could try fuslogvw.exe (Windows/.NET SDK) to monitor CLR fusion load failures.
Keith Hill
You could also load those two assemblies into ILDASM, look at the manifest to see what other assemblies they depend on.
Keith Hill
OK, went ahead and downloaded your ZIP - it runs with no errors and adds the type SQLParser on my Windows 7 x64 machine.
Keith Hill
Looking at this under fuslogvw - the only other assembly that gets loaded is Microsoft.JScript by Microsoft.PowerShell.Commands.Utility which is where Add-Type is located.
Keith Hill
Thanks Keith,BTW -- Here's how you return a parsed SQL batch $sqlparser = new-object SQLParser Sql100,$false,$sqlTextWhere Sql1000 is SQL 20008, quoted identifer on and sqlText is some valid T-SQL statement.The strange thing is it works fine on my Vista x86 machine, but errors on my Windows 7 x86 and Windows XP. I was thinking it has something to do with my paths. But I'll take a look at the utilities you mentioned.
Chad Miller
Keith, did you `installutil` those assemblies at some point?
Jaykul
No I didn't installutil them. They appear to be plain ol' .NET assemblies.
Keith Hill
+1  A: 

Hi Chad,

I have a sample I've use during our PS track. It's kinda basic but works. Here's the code using SMO:

$Assem = ("Microsoft.SqlServer.Smo","Microsoft.SqlServer.ConnectionInfo")
$Source = @"
public class MyMSSql
{
       public static string getEdition(string sqlName)
       {
           string sqlEdition;
           Microsoft.SqlServer.Management.Smo.Server sname = new Microsoft.SqlServer.Management.Smo.Server(sqlName);
           sqlEdition = sname.Information.Edition;
           return sqlEdition;
       }
       public string getSqlEdition(string sqlName)
       {
           string sqlEdition;
           Microsoft.SqlServer.Management.Smo.Server sname = new Microsoft.SqlServer.Management.Smo.Server(sqlName);
           sqlEdition = sname.Information.Edition;
           return sqlEdition;
       }

}
"@;
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source
[MyMSSql]::getEdition("MAX-PCWIN1")
#Developer Edition (64-bit)

$MySQLobj = New-Object MyMSSql
$MySQLobj.getSqlEdition("MAX-PCWIN1")

Hope this will give you a hint.

Max

Max Trinida
Thanks -- Yes, I was there when you presented and I downloaded your demos to see some examples of add-type when I originally created the code I posted.
Chad Miller
+2  A: 

Pretty simple, once you know ;-)

Max's example works because those assemblies are in the GAC so they can be referenced by name. Your assemblies aren't, so they need to be referenced by path.

You don't need the Add-Type references at the top either, at least, not for that script -- just change your last couple of lines to this:

$PSScriptRoot = (Split-Path $MyInvocation.MyCommand.Path -Parent)
$refs = @("$PSScriptRoot\Microsoft.Data.Schema.ScriptDom.dll","$PSScriptRoot\Microsoft.Data.Schema.ScriptDom.Sql.dll")
add-type -ReferencedAssemblies $refs -TypeDefinition $Source -Language CSharpVersion3 -passThru
Jaykul
OMG, That was simple and it works! Thank you so much.
Chad Miller