├── src ├── ScriptSqlConfig │ ├── Fakes │ │ └── Microsoft.SqlServer.ConnectionInfo.fakes │ ├── ExtensionMethods.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Helper.cs │ ├── app.config │ ├── ScriptSqlConfig.csproj │ ├── Options.cs │ └── Program.cs └── ScriptSqlConfig.sln ├── README.md └── .gitignore /src/ScriptSqlConfig/Fakes/Microsoft.SqlServer.ConnectionInfo.fakes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgraziano/ScriptSqlConfig/HEAD/src/ScriptSqlConfig/Fakes/Microsoft.SqlServer.ConnectionInfo.fakes -------------------------------------------------------------------------------- /src/ScriptSqlConfig/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | //using System.Linq; 5 | using System.Text; 6 | 7 | namespace ScriptSqlConfig 8 | { 9 | public static class ExtensionMethods 10 | { 11 | public static void Append(this StringCollection original, StringCollection newCollection) 12 | { 13 | foreach (string s in newCollection) 14 | { 15 | original.Add(s); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptSqlConfig", "ScriptSqlConfig\ScriptSqlConfig.csproj", "{4D994A2D-E861-4F50-9064-AB7281874095}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {4D994A2D-E861-4F50-9064-AB7281874095}.Debug|x86.ActiveCfg = Debug|x86 13 | {4D994A2D-E861-4F50-9064-AB7281874095}.Debug|x86.Build.0 = Debug|x86 14 | {4D994A2D-E861-4F50-9064-AB7281874095}.Release|x86.ActiveCfg = Release|x86 15 | {4D994A2D-E861-4F50-9064-AB7281874095}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ScriptSqlConfig")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("scaleSQL Consulting, LLC")] 12 | [assembly: AssemblyProduct("ScriptSqlConfig")] 13 | [assembly: AssemblyCopyright("Copyright © scaleSQL Consulting 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ba725e42-979f-4acc-9cd6-bb798f6894f4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2012.5")] 36 | [assembly: AssemblyFileVersion("2012.5")] 37 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using System.Reflection; 7 | 8 | namespace ScriptSqlConfig 9 | { 10 | class Helper 11 | { 12 | public static void TestSMO() 13 | { 14 | /* Display the SMO version */ 15 | //var ver = System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Smo.Server)).GetName().Version; 16 | //Program.WriteMessage("SMO Version: " + ver.ToString()); 17 | 18 | Assembly a1 = System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Smo.Server)); 19 | Program.WriteMessage("Microsoft.SqlServer.Smo:"); 20 | DisplayAssemblyDetails(a1); 21 | 22 | Assembly a2 = System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Common.ConnectionManager)); 23 | Program.WriteMessage(""); 24 | Program.WriteMessage("Microsoft.SqlServer.ConnectionInfo: "); 25 | DisplayAssemblyDetails(a2); 26 | 27 | Program.WriteMessage(""); 28 | Program.WriteMessage("Microsoft.SqlServer.ConnectionInfoExtended:"); 29 | DisplayAssemblyDetails(System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Trace.TraceFile))); 30 | 31 | Program.WriteMessage(""); 32 | Program.WriteMessage("Microsoft.SqlServer.Management.Sdk.Sfc:"); 33 | DisplayAssemblyDetails(System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Sdk.Sfc.DataProvider))); 34 | 35 | Program.WriteMessage(""); 36 | Program.WriteMessage("Microsoft.SqlServer.SmoExtended:"); 37 | DisplayAssemblyDetails(System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Smo.Backup))); 38 | 39 | 40 | Program.WriteMessage(""); 41 | Program.WriteMessage("Microsoft.SqlServer.SqlEnum:"); 42 | DisplayAssemblyDetails(System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Dmf.PolicyHealthState))); 43 | 44 | 45 | //#if DEBUG 46 | // Console.WriteLine("Press any key to continue...."); 47 | // Console.ReadLine(); 48 | //#endif 49 | } 50 | 51 | public static void DisplayAssemblyDetails(Assembly asm) 52 | { 53 | foreach (string s in asm.FullName.Split(',')) 54 | { 55 | Program.WriteMessage("\t" + s.Trim()); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Script SQL Server Configuration 2 | =============== 3 | Script SQL Server configuration information in a format suitable for disaster recovery purposes or checking into a source control system. 4 | 5 | Server level objects (linked servers, audits, etc.) are scripted in one file per object type. This makes DR easier and reviewing history possible. Database level objects (tables, functions, etc.) are scripted in one file per object. This makes reviewing object history easy and DR possible. The database backup is the primary DR mechanism for databases. 6 | 7 | The following instance level objects are scripted: 8 | 9 | * Logins These are scripted with the proper SID and optionally the hashed password. 10 | * Jobs 11 | * Linked servers 12 | * Audits 13 | * Alerts 14 | * Credentials 15 | * Proxy Accounts 16 | * Database Mail 17 | * Event Notifications 18 | * User-Defined Endpoints 19 | 20 | The following instance level objects and current values are written to a file: 21 | 22 | * Properties 23 | * Options 24 | 25 | User objects in master, model and msdb are scripted. 26 | 27 | The following user database objects can be scripted: 28 | 29 | * Tables 30 | * Stored procedures 31 | * User-defined data types 32 | * Views 33 | * Triggers 34 | * Table types 35 | * User-defined functions 36 | * Users, Database roles and application roles 37 | * Service Broker Objects 38 | 39 | Requirements 40 | ------------ 41 | This requires a SQL Server installation from SQL Server 2008, 2008 R2, 2012, or 2014. 42 | 43 | You can control which version of SMO is loaded by updating the `ScriptSqlConfig.exe.config` file. 44 | 45 | By default it loads the SQL Server 2012 version of SMO. To change this, uncomment the `` section of the `ScriptSqlConfig.exe.config` file. 46 | You'll see a number of entries that look like this: 47 | 48 | 49 | 52 | Assembly versions can be redirected in application, 53 | publisher policy, or machine configuration files. 54 | 55 | 56 | 57 | Simply update the `newVersion` attribute with the version of SMO you'd like to target. 58 | A comment in the `ScriptSqlConfig.exe.config` files includes instructions and the version numbers. 59 | 60 | 61 | Notes 62 | ----- 63 | Earlier versions of the source code and a 2008 version can be found on CodePlex at https://scriptsqlconfig.codeplex.com/. 64 | This shouldn't be needed now since this version can load the 2008 SMO libraries if needed. -------------------------------------------------------------------------------- /src/ScriptSqlConfig/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | 24 | 25 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ 175 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig/ScriptSqlConfig.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {4D994A2D-E861-4F50-9064-AB7281874095} 9 | Exe 10 | Properties 11 | ScriptSqlConfig 12 | ScriptSqlConfig 13 | v3.5 14 | 15 | 16 | 512 17 | 18 | 19 | x86 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | x86 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | FakesAssemblies\Microsoft.SqlServer.ConnectionInfo.11.0.0.0.Fakes.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Designer 63 | 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig/Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // 7 | // Copyright (C) 2008 Novell (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | // Compile With: 30 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 31 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 32 | // 33 | // The LINQ version just changes the implementation of 34 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 35 | 36 | // 37 | // A Getopt::Long-inspired option parsing library for C#. 38 | // 39 | // NDesk.Options.OptionSet is built upon a key/value table, where the 40 | // key is a option format string and the value is a delegate that is 41 | // invoked when the format string is matched. 42 | // 43 | // Option format strings: 44 | // Regex-like BNF Grammar: 45 | // name: .+ 46 | // type: [=:] 47 | // sep: ( [^{}]+ | '{' .+ '}' )? 48 | // aliases: ( name type sep ) ( '|' name type sep )* 49 | // 50 | // Each '|'-delimited name is an alias for the associated action. If the 51 | // format string ends in a '=', it has a required value. If the format 52 | // string ends in a ':', it has an optional value. If neither '=' or ':' 53 | // is present, no value is supported. `=' or `:' need only be defined on one 54 | // alias, but if they are provided on more than one they must be consistent. 55 | // 56 | // Each alias portion may also end with a "key/value separator", which is used 57 | // to split option values if the option accepts > 1 value. If not specified, 58 | // it defaults to '=' and ':'. If specified, it can be any character except 59 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 60 | // used (i.e. the separate values should be distinct arguments), then "{}" 61 | // should be used as the separator. 62 | // 63 | // Options are extracted either from the current option by looking for 64 | // the option name followed by an '=' or ':', or is taken from the 65 | // following option IFF: 66 | // - The current option does not contain a '=' or a ':' 67 | // - The current option requires a value (i.e. not a Option type of ':') 68 | // 69 | // The `name' used in the option format string does NOT include any leading 70 | // option indicator, such as '-', '--', or '/'. All three of these are 71 | // permitted/required on any named option. 72 | // 73 | // Option bundling is permitted so long as: 74 | // - '-' is used to start the option group 75 | // - all of the bundled options are a single character 76 | // - at most one of the bundled options accepts a value, and the value 77 | // provided starts from the next character to the end of the string. 78 | // 79 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 80 | // as '-Dname=value'. 81 | // 82 | // Option processing is disabled by specifying "--". All options after "--" 83 | // are returned by OptionSet.Parse() unchanged and unprocessed. 84 | // 85 | // Unprocessed options are returned from OptionSet.Parse(). 86 | // 87 | // Examples: 88 | // int verbose = 0; 89 | // OptionSet p = new OptionSet () 90 | // .Add ("v", v => ++verbose) 91 | // .Add ("name=|value=", v => Console.WriteLine (v)); 92 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 93 | // 94 | // The above would parse the argument string array, and would invoke the 95 | // lambda expression three times, setting `verbose' to 3 when complete. 96 | // It would also print out "A" and "B" to standard output. 97 | // The returned array would contain the string "extra". 98 | // 99 | // C# 3.0 collection initializers are supported and encouraged: 100 | // var p = new OptionSet () { 101 | // { "h|?|help", v => ShowHelp () }, 102 | // }; 103 | // 104 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 105 | // custom data types in the callback type; TypeConverter.ConvertFromString() 106 | // is used to convert the value option to an instance of the specified 107 | // type: 108 | // 109 | // var p = new OptionSet () { 110 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 111 | // }; 112 | // 113 | // Random other tidbits: 114 | // - Boolean options (those w/o '=' or ':' in the option format string) 115 | // are explicitly enabled if they are followed with '+', and explicitly 116 | // disabled if they are followed with '-': 117 | // string a = null; 118 | // var p = new OptionSet () { 119 | // { "a", s => a = s }, 120 | // }; 121 | // p.Parse (new string[]{"-a"}); // sets v != null 122 | // p.Parse (new string[]{"-a+"}); // sets v != null 123 | // p.Parse (new string[]{"-a-"}); // sets v == null 124 | // 125 | 126 | using System; 127 | using System.Collections; 128 | using System.Collections.Generic; 129 | using System.Collections.ObjectModel; 130 | using System.ComponentModel; 131 | using System.Globalization; 132 | using System.IO; 133 | using System.Runtime.Serialization; 134 | using System.Security.Permissions; 135 | using System.Text; 136 | using System.Text.RegularExpressions; 137 | 138 | #if LINQ 139 | using System.Linq; 140 | #endif 141 | 142 | #if TEST 143 | using NDesk.Options; 144 | #endif 145 | 146 | namespace NDesk.Options { 147 | 148 | public class OptionValueCollection : IList, IList { 149 | 150 | List values = new List (); 151 | OptionContext c; 152 | 153 | internal OptionValueCollection (OptionContext c) 154 | { 155 | this.c = c; 156 | } 157 | 158 | #region ICollection 159 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} 160 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} 161 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} 162 | #endregion 163 | 164 | #region ICollection 165 | public void Add (string item) {values.Add (item);} 166 | public void Clear () {values.Clear ();} 167 | public bool Contains (string item) {return values.Contains (item);} 168 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} 169 | public bool Remove (string item) {return values.Remove (item);} 170 | public int Count {get {return values.Count;}} 171 | public bool IsReadOnly {get {return false;}} 172 | #endregion 173 | 174 | #region IEnumerable 175 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} 176 | #endregion 177 | 178 | #region IEnumerable 179 | public IEnumerator GetEnumerator () {return values.GetEnumerator ();} 180 | #endregion 181 | 182 | #region IList 183 | int IList.Add (object value) {return (values as IList).Add (value);} 184 | bool IList.Contains (object value) {return (values as IList).Contains (value);} 185 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} 186 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} 187 | void IList.Remove (object value) {(values as IList).Remove (value);} 188 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} 189 | bool IList.IsFixedSize {get {return false;}} 190 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} 191 | #endregion 192 | 193 | #region IList 194 | public int IndexOf (string item) {return values.IndexOf (item);} 195 | public void Insert (int index, string item) {values.Insert (index, item);} 196 | public void RemoveAt (int index) {values.RemoveAt (index);} 197 | 198 | private void AssertValid (int index) 199 | { 200 | if (c.Option == null) 201 | throw new InvalidOperationException ("OptionContext.Option is null."); 202 | if (index >= c.Option.MaxValueCount) 203 | throw new ArgumentOutOfRangeException ("index"); 204 | if (c.Option.OptionValueType == OptionValueType.Required && 205 | index >= values.Count) 206 | throw new OptionException (string.Format ( 207 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 208 | c.OptionName); 209 | } 210 | 211 | public string this [int index] { 212 | get { 213 | AssertValid (index); 214 | return index >= values.Count ? null : values [index]; 215 | } 216 | set { 217 | values [index] = value; 218 | } 219 | } 220 | #endregion 221 | 222 | public List ToList () 223 | { 224 | return new List (values); 225 | } 226 | 227 | public string[] ToArray () 228 | { 229 | return values.ToArray (); 230 | } 231 | 232 | public override string ToString () 233 | { 234 | return string.Join (", ", values.ToArray ()); 235 | } 236 | } 237 | 238 | public class OptionContext { 239 | private Option option; 240 | private string name; 241 | private int index; 242 | private OptionSet set; 243 | private OptionValueCollection c; 244 | 245 | public OptionContext (OptionSet set) 246 | { 247 | this.set = set; 248 | this.c = new OptionValueCollection (this); 249 | } 250 | 251 | public Option Option { 252 | get {return option;} 253 | set {option = value;} 254 | } 255 | 256 | public string OptionName { 257 | get {return name;} 258 | set {name = value;} 259 | } 260 | 261 | public int OptionIndex { 262 | get {return index;} 263 | set {index = value;} 264 | } 265 | 266 | public OptionSet OptionSet { 267 | get {return set;} 268 | } 269 | 270 | public OptionValueCollection OptionValues { 271 | get {return c;} 272 | } 273 | } 274 | 275 | public enum OptionValueType { 276 | None, 277 | Optional, 278 | Required, 279 | } 280 | 281 | public abstract class Option { 282 | string prototype, description; 283 | string[] names; 284 | OptionValueType type; 285 | int count; 286 | string[] separators; 287 | 288 | protected Option (string prototype, string description) 289 | : this (prototype, description, 1) 290 | { 291 | } 292 | 293 | protected Option (string prototype, string description, int maxValueCount) 294 | { 295 | if (prototype == null) 296 | throw new ArgumentNullException ("prototype"); 297 | if (prototype.Length == 0) 298 | throw new ArgumentException ("Cannot be the empty string.", "prototype"); 299 | if (maxValueCount < 0) 300 | throw new ArgumentOutOfRangeException ("maxValueCount"); 301 | 302 | this.prototype = prototype; 303 | this.names = prototype.Split ('|'); 304 | this.description = description; 305 | this.count = maxValueCount; 306 | this.type = ParsePrototype (); 307 | 308 | if (this.count == 0 && type != OptionValueType.None) 309 | throw new ArgumentException ( 310 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 311 | "OptionValueType.Optional.", 312 | "maxValueCount"); 313 | if (this.type == OptionValueType.None && maxValueCount > 1) 314 | throw new ArgumentException ( 315 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 316 | "maxValueCount"); 317 | if (Array.IndexOf (names, "<>") >= 0 && 318 | ((names.Length == 1 && this.type != OptionValueType.None) || 319 | (names.Length > 1 && this.MaxValueCount > 1))) 320 | throw new ArgumentException ( 321 | "The default option handler '<>' cannot require values.", 322 | "prototype"); 323 | } 324 | 325 | public string Prototype {get {return prototype;}} 326 | public string Description {get {return description;}} 327 | public OptionValueType OptionValueType {get {return type;}} 328 | public int MaxValueCount {get {return count;}} 329 | 330 | public string[] GetNames () 331 | { 332 | return (string[]) names.Clone (); 333 | } 334 | 335 | public string[] GetValueSeparators () 336 | { 337 | if (separators == null) 338 | return new string [0]; 339 | return (string[]) separators.Clone (); 340 | } 341 | 342 | protected static T Parse (string value, OptionContext c) 343 | { 344 | TypeConverter conv = TypeDescriptor.GetConverter (typeof (T)); 345 | T t = default (T); 346 | try { 347 | if (value != null) 348 | t = (T) conv.ConvertFromString (value); 349 | } 350 | catch (Exception e) { 351 | throw new OptionException ( 352 | string.Format ( 353 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), 354 | value, typeof (T).Name, c.OptionName), 355 | c.OptionName, e); 356 | } 357 | return t; 358 | } 359 | 360 | internal string[] Names {get {return names;}} 361 | internal string[] ValueSeparators {get {return separators;}} 362 | 363 | static readonly char[] NameTerminator = new char[]{'=', ':'}; 364 | 365 | private OptionValueType ParsePrototype () 366 | { 367 | char type = '\0'; 368 | List seps = new List (); 369 | for (int i = 0; i < names.Length; ++i) { 370 | string name = names [i]; 371 | if (name.Length == 0) 372 | throw new ArgumentException ("Empty option names are not supported.", "prototype"); 373 | 374 | int end = name.IndexOfAny (NameTerminator); 375 | if (end == -1) 376 | continue; 377 | names [i] = name.Substring (0, end); 378 | if (type == '\0' || type == name [end]) 379 | type = name [end]; 380 | else 381 | throw new ArgumentException ( 382 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), 383 | "prototype"); 384 | AddSeparators (name, end, seps); 385 | } 386 | 387 | if (type == '\0') 388 | return OptionValueType.None; 389 | 390 | if (count <= 1 && seps.Count != 0) 391 | throw new ArgumentException ( 392 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), 393 | "prototype"); 394 | if (count > 1) { 395 | if (seps.Count == 0) 396 | this.separators = new string[]{":", "="}; 397 | else if (seps.Count == 1 && seps [0].Length == 0) 398 | this.separators = null; 399 | else 400 | this.separators = seps.ToArray (); 401 | } 402 | 403 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 404 | } 405 | 406 | private static void AddSeparators (string name, int end, ICollection seps) 407 | { 408 | int start = -1; 409 | for (int i = end+1; i < name.Length; ++i) { 410 | switch (name [i]) { 411 | case '{': 412 | if (start != -1) 413 | throw new ArgumentException ( 414 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 415 | "prototype"); 416 | start = i+1; 417 | break; 418 | case '}': 419 | if (start == -1) 420 | throw new ArgumentException ( 421 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 422 | "prototype"); 423 | seps.Add (name.Substring (start, i-start)); 424 | start = -1; 425 | break; 426 | default: 427 | if (start == -1) 428 | seps.Add (name [i].ToString ()); 429 | break; 430 | } 431 | } 432 | if (start != -1) 433 | throw new ArgumentException ( 434 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 435 | "prototype"); 436 | } 437 | 438 | public void Invoke (OptionContext c) 439 | { 440 | OnParseComplete (c); 441 | c.OptionName = null; 442 | c.Option = null; 443 | c.OptionValues.Clear (); 444 | } 445 | 446 | protected abstract void OnParseComplete (OptionContext c); 447 | 448 | public override string ToString () 449 | { 450 | return Prototype; 451 | } 452 | } 453 | 454 | [Serializable] 455 | public class OptionException : Exception { 456 | private string option; 457 | 458 | public OptionException () 459 | { 460 | } 461 | 462 | public OptionException (string message, string optionName) 463 | : base (message) 464 | { 465 | this.option = optionName; 466 | } 467 | 468 | public OptionException (string message, string optionName, Exception innerException) 469 | : base (message, innerException) 470 | { 471 | this.option = optionName; 472 | } 473 | 474 | protected OptionException (SerializationInfo info, StreamingContext context) 475 | : base (info, context) 476 | { 477 | this.option = info.GetString ("OptionName"); 478 | } 479 | 480 | public string OptionName { 481 | get {return this.option;} 482 | } 483 | 484 | [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] 485 | public override void GetObjectData (SerializationInfo info, StreamingContext context) 486 | { 487 | base.GetObjectData (info, context); 488 | info.AddValue ("OptionName", option); 489 | } 490 | } 491 | 492 | public delegate void OptionAction (TKey key, TValue value); 493 | 494 | public class OptionSet : KeyedCollection 495 | { 496 | public OptionSet () 497 | : this (delegate (string f) {return f;}) 498 | { 499 | } 500 | 501 | public OptionSet (Converter localizer) 502 | { 503 | this.localizer = localizer; 504 | } 505 | 506 | Converter localizer; 507 | 508 | public Converter MessageLocalizer { 509 | get {return localizer;} 510 | } 511 | 512 | protected override string GetKeyForItem (Option item) 513 | { 514 | if (item == null) 515 | throw new ArgumentNullException ("option"); 516 | if (item.Names != null && item.Names.Length > 0) 517 | return item.Names [0]; 518 | // This should never happen, as it's invalid for Option to be 519 | // constructed w/o any names. 520 | throw new InvalidOperationException ("Option has no names!"); 521 | } 522 | 523 | [Obsolete ("Use KeyedCollection.this[string]")] 524 | protected Option GetOptionForName (string option) 525 | { 526 | if (option == null) 527 | throw new ArgumentNullException ("option"); 528 | try { 529 | return base [option]; 530 | } 531 | catch (KeyNotFoundException) { 532 | return null; 533 | } 534 | } 535 | 536 | protected override void InsertItem (int index, Option item) 537 | { 538 | base.InsertItem (index, item); 539 | AddImpl (item); 540 | } 541 | 542 | protected override void RemoveItem (int index) 543 | { 544 | base.RemoveItem (index); 545 | Option p = Items [index]; 546 | // KeyedCollection.RemoveItem() handles the 0th item 547 | for (int i = 1; i < p.Names.Length; ++i) { 548 | Dictionary.Remove (p.Names [i]); 549 | } 550 | } 551 | 552 | protected override void SetItem (int index, Option item) 553 | { 554 | base.SetItem (index, item); 555 | RemoveItem (index); 556 | AddImpl (item); 557 | } 558 | 559 | private void AddImpl (Option option) 560 | { 561 | if (option == null) 562 | throw new ArgumentNullException ("option"); 563 | List added = new List (option.Names.Length); 564 | try { 565 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 566 | for (int i = 1; i < option.Names.Length; ++i) { 567 | Dictionary.Add (option.Names [i], option); 568 | added.Add (option.Names [i]); 569 | } 570 | } 571 | catch (Exception) { 572 | foreach (string name in added) 573 | Dictionary.Remove (name); 574 | throw; 575 | } 576 | } 577 | 578 | public new OptionSet Add (Option option) 579 | { 580 | base.Add (option); 581 | return this; 582 | } 583 | 584 | sealed class ActionOption : Option { 585 | Action action; 586 | 587 | public ActionOption (string prototype, string description, int count, Action action) 588 | : base (prototype, description, count) 589 | { 590 | if (action == null) 591 | throw new ArgumentNullException ("action"); 592 | this.action = action; 593 | } 594 | 595 | protected override void OnParseComplete (OptionContext c) 596 | { 597 | action (c.OptionValues); 598 | } 599 | } 600 | 601 | public OptionSet Add (string prototype, Action action) 602 | { 603 | return Add (prototype, null, action); 604 | } 605 | 606 | public OptionSet Add (string prototype, string description, Action action) 607 | { 608 | if (action == null) 609 | throw new ArgumentNullException ("action"); 610 | Option p = new ActionOption (prototype, description, 1, 611 | delegate (OptionValueCollection v) { action (v [0]); }); 612 | base.Add (p); 613 | return this; 614 | } 615 | 616 | public OptionSet Add (string prototype, OptionAction action) 617 | { 618 | return Add (prototype, null, action); 619 | } 620 | 621 | public OptionSet Add (string prototype, string description, OptionAction action) 622 | { 623 | if (action == null) 624 | throw new ArgumentNullException ("action"); 625 | Option p = new ActionOption (prototype, description, 2, 626 | delegate (OptionValueCollection v) {action (v [0], v [1]);}); 627 | base.Add (p); 628 | return this; 629 | } 630 | 631 | sealed class ActionOption : Option { 632 | Action action; 633 | 634 | public ActionOption (string prototype, string description, Action action) 635 | : base (prototype, description, 1) 636 | { 637 | if (action == null) 638 | throw new ArgumentNullException ("action"); 639 | this.action = action; 640 | } 641 | 642 | protected override void OnParseComplete (OptionContext c) 643 | { 644 | action (Parse (c.OptionValues [0], c)); 645 | } 646 | } 647 | 648 | sealed class ActionOption : Option { 649 | OptionAction action; 650 | 651 | public ActionOption (string prototype, string description, OptionAction action) 652 | : base (prototype, description, 2) 653 | { 654 | if (action == null) 655 | throw new ArgumentNullException ("action"); 656 | this.action = action; 657 | } 658 | 659 | protected override void OnParseComplete (OptionContext c) 660 | { 661 | action ( 662 | Parse (c.OptionValues [0], c), 663 | Parse (c.OptionValues [1], c)); 664 | } 665 | } 666 | 667 | public OptionSet Add (string prototype, Action action) 668 | { 669 | return Add (prototype, null, action); 670 | } 671 | 672 | public OptionSet Add (string prototype, string description, Action action) 673 | { 674 | return Add (new ActionOption (prototype, description, action)); 675 | } 676 | 677 | public OptionSet Add (string prototype, OptionAction action) 678 | { 679 | return Add (prototype, null, action); 680 | } 681 | 682 | public OptionSet Add (string prototype, string description, OptionAction action) 683 | { 684 | return Add (new ActionOption (prototype, description, action)); 685 | } 686 | 687 | protected virtual OptionContext CreateOptionContext () 688 | { 689 | return new OptionContext (this); 690 | } 691 | 692 | #if LINQ 693 | public List Parse (IEnumerable arguments) 694 | { 695 | bool process = true; 696 | OptionContext c = CreateOptionContext (); 697 | c.OptionIndex = -1; 698 | var def = GetOptionForName ("<>"); 699 | var unprocessed = 700 | from argument in arguments 701 | where ++c.OptionIndex >= 0 && (process || def != null) 702 | ? process 703 | ? argument == "--" 704 | ? (process = false) 705 | : !Parse (argument, c) 706 | ? def != null 707 | ? Unprocessed (null, def, c, argument) 708 | : true 709 | : false 710 | : def != null 711 | ? Unprocessed (null, def, c, argument) 712 | : true 713 | : true 714 | select argument; 715 | List r = unprocessed.ToList (); 716 | if (c.Option != null) 717 | c.Option.Invoke (c); 718 | return r; 719 | } 720 | #else 721 | public List Parse (IEnumerable arguments) 722 | { 723 | OptionContext c = CreateOptionContext (); 724 | c.OptionIndex = -1; 725 | bool process = true; 726 | List unprocessed = new List (); 727 | Option def = Contains ("<>") ? this ["<>"] : null; 728 | foreach (string argument in arguments) { 729 | ++c.OptionIndex; 730 | if (argument == "--") { 731 | process = false; 732 | continue; 733 | } 734 | if (!process) { 735 | Unprocessed (unprocessed, def, c, argument); 736 | continue; 737 | } 738 | if (!Parse (argument, c)) 739 | Unprocessed (unprocessed, def, c, argument); 740 | } 741 | if (c.Option != null) 742 | c.Option.Invoke (c); 743 | return unprocessed; 744 | } 745 | #endif 746 | 747 | private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) 748 | { 749 | if (def == null) { 750 | extra.Add (argument); 751 | return false; 752 | } 753 | c.OptionValues.Add (argument); 754 | c.Option = def; 755 | c.Option.Invoke (c); 756 | return false; 757 | } 758 | 759 | private readonly Regex ValueOption = new Regex ( 760 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 761 | 762 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) 763 | { 764 | if (argument == null) 765 | throw new ArgumentNullException ("argument"); 766 | 767 | flag = name = sep = value = null; 768 | Match m = ValueOption.Match (argument); 769 | if (!m.Success) { 770 | return false; 771 | } 772 | flag = m.Groups ["flag"].Value; 773 | name = m.Groups ["name"].Value; 774 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { 775 | sep = m.Groups ["sep"].Value; 776 | value = m.Groups ["value"].Value; 777 | } 778 | return true; 779 | } 780 | 781 | protected virtual bool Parse (string argument, OptionContext c) 782 | { 783 | if (c.Option != null) { 784 | ParseValue (argument, c); 785 | return true; 786 | } 787 | 788 | string f, n, s, v; 789 | if (!GetOptionParts (argument, out f, out n, out s, out v)) 790 | return false; 791 | 792 | Option p; 793 | if (Contains (n)) { 794 | p = this [n]; 795 | c.OptionName = f + n; 796 | c.Option = p; 797 | switch (p.OptionValueType) { 798 | case OptionValueType.None: 799 | c.OptionValues.Add (n); 800 | c.Option.Invoke (c); 801 | break; 802 | case OptionValueType.Optional: 803 | case OptionValueType.Required: 804 | ParseValue (v, c); 805 | break; 806 | } 807 | return true; 808 | } 809 | // no match; is it a bool option? 810 | if (ParseBool (argument, n, c)) 811 | return true; 812 | // is it a bundled option? 813 | if (ParseBundledValue (f, string.Concat (n + s + v), c)) 814 | return true; 815 | 816 | return false; 817 | } 818 | 819 | private void ParseValue (string option, OptionContext c) 820 | { 821 | if (option != null) 822 | foreach (string o in c.Option.ValueSeparators != null 823 | ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) 824 | : new string[]{option}) { 825 | c.OptionValues.Add (o); 826 | } 827 | if (c.OptionValues.Count == c.Option.MaxValueCount || 828 | c.Option.OptionValueType == OptionValueType.Optional) 829 | c.Option.Invoke (c); 830 | else if (c.OptionValues.Count > c.Option.MaxValueCount) { 831 | throw new OptionException (localizer (string.Format ( 832 | "Error: Found {0} option values when expecting {1}.", 833 | c.OptionValues.Count, c.Option.MaxValueCount)), 834 | c.OptionName); 835 | } 836 | } 837 | 838 | private bool ParseBool (string option, string n, OptionContext c) 839 | { 840 | Option p; 841 | string rn; 842 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && 843 | Contains ((rn = n.Substring (0, n.Length-1)))) { 844 | p = this [rn]; 845 | string v = n [n.Length-1] == '+' ? option : null; 846 | c.OptionName = option; 847 | c.Option = p; 848 | c.OptionValues.Add (v); 849 | p.Invoke (c); 850 | return true; 851 | } 852 | return false; 853 | } 854 | 855 | private bool ParseBundledValue (string f, string n, OptionContext c) 856 | { 857 | if (f != "-") 858 | return false; 859 | for (int i = 0; i < n.Length; ++i) { 860 | Option p; 861 | string opt = f + n [i].ToString (); 862 | string rn = n [i].ToString (); 863 | if (!Contains (rn)) { 864 | if (i == 0) 865 | return false; 866 | throw new OptionException (string.Format (localizer ( 867 | "Cannot bundle unregistered option '{0}'."), opt), opt); 868 | } 869 | p = this [rn]; 870 | switch (p.OptionValueType) { 871 | case OptionValueType.None: 872 | Invoke (c, opt, n, p); 873 | break; 874 | case OptionValueType.Optional: 875 | case OptionValueType.Required: { 876 | string v = n.Substring (i+1); 877 | c.Option = p; 878 | c.OptionName = opt; 879 | ParseValue (v.Length != 0 ? v : null, c); 880 | return true; 881 | } 882 | default: 883 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); 884 | } 885 | } 886 | return true; 887 | } 888 | 889 | private static void Invoke (OptionContext c, string name, string value, Option option) 890 | { 891 | c.OptionName = name; 892 | c.Option = option; 893 | c.OptionValues.Add (value); 894 | option.Invoke (c); 895 | } 896 | 897 | private const int OptionWidth = 29; 898 | 899 | public void WriteOptionDescriptions (TextWriter o) 900 | { 901 | foreach (Option p in this) { 902 | int written = 0; 903 | if (!WriteOptionPrototype (o, p, ref written)) 904 | continue; 905 | 906 | if (written < OptionWidth) 907 | o.Write (new string (' ', OptionWidth - written)); 908 | else { 909 | o.WriteLine (); 910 | o.Write (new string (' ', OptionWidth)); 911 | } 912 | 913 | List lines = GetLines (localizer (GetDescription (p.Description))); 914 | o.WriteLine (lines [0]); 915 | string prefix = new string (' ', OptionWidth+2); 916 | for (int i = 1; i < lines.Count; ++i) { 917 | o.Write (prefix); 918 | o.WriteLine (lines [i]); 919 | } 920 | } 921 | } 922 | 923 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written) 924 | { 925 | string[] names = p.Names; 926 | 927 | int i = GetNextOptionIndex (names, 0); 928 | if (i == names.Length) 929 | return false; 930 | 931 | if (names [i].Length == 1) { 932 | Write (o, ref written, " -"); 933 | Write (o, ref written, names [0]); 934 | } 935 | else { 936 | Write (o, ref written, " --"); 937 | Write (o, ref written, names [0]); 938 | } 939 | 940 | for ( i = GetNextOptionIndex (names, i+1); 941 | i < names.Length; i = GetNextOptionIndex (names, i+1)) { 942 | Write (o, ref written, ", "); 943 | Write (o, ref written, names [i].Length == 1 ? "-" : "--"); 944 | Write (o, ref written, names [i]); 945 | } 946 | 947 | if (p.OptionValueType == OptionValueType.Optional || 948 | p.OptionValueType == OptionValueType.Required) { 949 | if (p.OptionValueType == OptionValueType.Optional) { 950 | Write (o, ref written, localizer ("[")); 951 | } 952 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); 953 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 954 | ? p.ValueSeparators [0] 955 | : " "; 956 | for (int c = 1; c < p.MaxValueCount; ++c) { 957 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); 958 | } 959 | if (p.OptionValueType == OptionValueType.Optional) { 960 | Write (o, ref written, localizer ("]")); 961 | } 962 | } 963 | return true; 964 | } 965 | 966 | static int GetNextOptionIndex (string[] names, int i) 967 | { 968 | while (i < names.Length && names [i] == "<>") { 969 | ++i; 970 | } 971 | return i; 972 | } 973 | 974 | static void Write (TextWriter o, ref int n, string s) 975 | { 976 | n += s.Length; 977 | o.Write (s); 978 | } 979 | 980 | private static string GetArgumentName (int index, int maxIndex, string description) 981 | { 982 | if (description == null) 983 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 984 | string[] nameStart; 985 | if (maxIndex == 1) 986 | nameStart = new string[]{"{0:", "{"}; 987 | else 988 | nameStart = new string[]{"{" + index + ":"}; 989 | for (int i = 0; i < nameStart.Length; ++i) { 990 | int start, j = 0; 991 | do { 992 | start = description.IndexOf (nameStart [i], j); 993 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); 994 | if (start == -1) 995 | continue; 996 | int end = description.IndexOf ("}", start); 997 | if (end == -1) 998 | continue; 999 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); 1000 | } 1001 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1002 | } 1003 | 1004 | private static string GetDescription (string description) 1005 | { 1006 | if (description == null) 1007 | return string.Empty; 1008 | StringBuilder sb = new StringBuilder (description.Length); 1009 | int start = -1; 1010 | for (int i = 0; i < description.Length; ++i) { 1011 | switch (description [i]) { 1012 | case '{': 1013 | if (i == start) { 1014 | sb.Append ('{'); 1015 | start = -1; 1016 | } 1017 | else if (start < 0) 1018 | start = i + 1; 1019 | break; 1020 | case '}': 1021 | if (start < 0) { 1022 | if ((i+1) == description.Length || description [i+1] != '}') 1023 | throw new InvalidOperationException ("Invalid option description: " + description); 1024 | ++i; 1025 | sb.Append ("}"); 1026 | } 1027 | else { 1028 | sb.Append (description.Substring (start, i - start)); 1029 | start = -1; 1030 | } 1031 | break; 1032 | case ':': 1033 | if (start < 0) 1034 | goto default; 1035 | start = i + 1; 1036 | break; 1037 | default: 1038 | if (start < 0) 1039 | sb.Append (description [i]); 1040 | break; 1041 | } 1042 | } 1043 | return sb.ToString (); 1044 | } 1045 | 1046 | private static List GetLines (string description) 1047 | { 1048 | List lines = new List (); 1049 | if (string.IsNullOrEmpty (description)) { 1050 | lines.Add (string.Empty); 1051 | return lines; 1052 | } 1053 | int length = 80 - OptionWidth - 2; 1054 | int start = 0, end; 1055 | do { 1056 | end = GetLineEnd (start, length, description); 1057 | bool cont = false; 1058 | if (end < description.Length) { 1059 | char c = description [end]; 1060 | if (c == '-' || (char.IsWhiteSpace (c) && c != '\n')) 1061 | ++end; 1062 | else if (c != '\n') { 1063 | cont = true; 1064 | --end; 1065 | } 1066 | } 1067 | lines.Add (description.Substring (start, end - start)); 1068 | if (cont) { 1069 | lines [lines.Count-1] += "-"; 1070 | } 1071 | start = end; 1072 | if (start < description.Length && description [start] == '\n') 1073 | ++start; 1074 | } while (end < description.Length); 1075 | return lines; 1076 | } 1077 | 1078 | private static int GetLineEnd (int start, int length, string description) 1079 | { 1080 | int end = Math.Min (start + length, description.Length); 1081 | int sep = -1; 1082 | for (int i = start; i < end; ++i) { 1083 | switch (description [i]) { 1084 | case ' ': 1085 | case '\t': 1086 | case '\v': 1087 | case '-': 1088 | case ',': 1089 | case '.': 1090 | case ';': 1091 | sep = i; 1092 | break; 1093 | case '\n': 1094 | return i; 1095 | } 1096 | } 1097 | if (sep == -1 || end == description.Length) 1098 | return end; 1099 | return sep; 1100 | } 1101 | } 1102 | } 1103 | 1104 | -------------------------------------------------------------------------------- /src/ScriptSqlConfig/Program.cs: -------------------------------------------------------------------------------- 1 | // ScriptSqlConfig - Generate a script of common SQL Server configration options and objects 2 | // 3 | // Copyright (c) 2011 scaleSQL Consulting, LLC 4 | // 5 | // Definitions 6 | // =========== 7 | // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same 8 | // meaning here as under U.S. copyright law. 9 | // A "contribution" is the original software, or any additions or changes to the software. 10 | // A "contributor" is any person that distributes its contribution under this license. 11 | // "Licensed patents" are a contributor's patent claims that read directly on its contribution. 12 | // 13 | // Grant of Rights 14 | // =============== 15 | // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and 16 | // limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 17 | // copyright license to reproduce its contribution, prepare derivative works of its contribution, 18 | // and distribute its contribution or any derivative works that you create. 19 | // (B) Patent Grant- Subject to the terms of this license, including the license conditions and 20 | // limitations in section 3, each contributor grants you a non-exclusive, worldwide, 21 | // royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, 22 | // import, and/or otherwise dispose of its contribution in the software or derivative works of 23 | // the contribution in the software. 24 | // 25 | // Conditions and Limitations 26 | // ========================== 27 | // (A) No Trademark License- This license does not grant you rights to use any contributors' 28 | // name, logo, or trademarks. 29 | // (B) If you bring a patent claim against any contributor over patents that you claim are 30 | // infringed by the software, your patent license from such contributor to the software ends automatically. 31 | // (C) If you distribute any portion of the software, you must retain all copyright, patent, 32 | // trademark, and attribution notices that are present in the software. 33 | // (D) If you distribute any portion of the software in source code form, you may do so 34 | // only under this license by including a complete copy of this license with your distribution. 35 | // If you distribute any portion of the software in compiled or object code form, you may only 36 | // do so under a license that complies with this license. 37 | // (E) The software is licensed "as-is." You bear the risk of using it. The contributors 38 | // give no express warranties, guarantees, or conditions. You may have additional consumer 39 | // rights under your local laws which this license cannot change. To the extent permitted 40 | // under your local laws, the contributors exclude the implied warranties of merchantability, 41 | // fitness for a particular purpose and non-infringement. 42 | 43 | using System; 44 | using System.Collections.Generic; 45 | using System.Text; 46 | 47 | using System.Data.Sql; 48 | using System.Data.SqlClient; 49 | using Microsoft.SqlServer.Management.Smo; 50 | using Microsoft.SqlServer.Management.Smo.Agent; 51 | using Microsoft.SqlServer.Management.Smo.Mail; 52 | using Microsoft.SqlServer.Management.Common; 53 | using System.Collections.Specialized; 54 | using System.IO; 55 | 56 | using System.Text.RegularExpressions; 57 | using NDesk.Options; 58 | 59 | using System.Reflection; 60 | using ScriptSqlConfig; 61 | 62 | namespace ScriptSqlConfig 63 | { 64 | class Program 65 | { 66 | static bool SCRIPT_INSTANCE = true; 67 | static bool SCRIPT_DATABASES = false; 68 | static bool VERBOSE = false; 69 | static string SERVER = ""; 70 | static string DIRECTORY = ""; 71 | static string DATABASE = ""; 72 | static bool SHOW_HELP = false; 73 | static string USER_NAME = ""; 74 | static string PASSWORD = ""; 75 | static bool TEST_SMO = false; 76 | static bool HASHEDPASSWORDS = false; 77 | 78 | 79 | static System.Version SERVER_VERSION = new Version(); 80 | static System.Version SMO_VERSION = new Version(); 81 | 82 | static void Main(string[] args) 83 | { 84 | Version v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; 85 | 86 | // use tfpt from the TFS Power Tools 87 | // that includes a tfpt that adds all files not in TFS. 88 | 89 | var p = new OptionSet () { 90 | { "v|verbose", z => VERBOSE = true }, 91 | { "s|server=", z => SERVER = z }, 92 | { "db|databases", z => { SCRIPT_DATABASES = true; } }, 93 | // { "nodb", z => { SCRIPT_DATABASES = false; } }, 94 | { "noinstance", z => { SCRIPT_INSTANCE = false; } }, 95 | { "d|dir|directory=", z => DIRECTORY = z } , 96 | { "scriptdb=", z => DATABASE = z } , 97 | { "u|user=", z => USER_NAME = z }, 98 | { "p|password=", z => PASSWORD = z }, 99 | { "testsmo", z => TEST_SMO = true }, 100 | { "hashedpasswords", z => HASHEDPASSWORDS = true }, 101 | { "h|?|help", z => { SHOW_HELP = true; } } 102 | }; 103 | 104 | List extra = p.Parse (args); 105 | 106 | // write a message if we find the /nodb flag 107 | if (extra.Contains("/nodb") || extra.Contains("/NODB")) 108 | { 109 | Console.WriteLine(""); 110 | Console.WriteLine("*****************************************************"); 111 | Console.WriteLine("*** WARNING: The /nodb option is no longer supported."); 112 | Console.WriteLine("*** Databases aren't scripted by default."); 113 | Console.WriteLine("*****************************************************"); 114 | Console.WriteLine(""); 115 | } 116 | 117 | WriteMessage("Launching (" + v.Major.ToString() + "." + v.Minor.ToString() + ")...."); 118 | if (TEST_SMO) 119 | { 120 | Helper.TestSMO(); 121 | // return; 122 | } 123 | 124 | // the server and directory are required. No args also brings out the help 125 | if (SERVER.Length == 0 || DIRECTORY.Length == 0 || args.Length == 0) 126 | SHOW_HELP = true; 127 | 128 | // if they enter a username, require a password. 129 | if (USER_NAME.Length > 0 && PASSWORD.Length == 0) 130 | SHOW_HELP = true; 131 | 132 | 133 | #region Show Help 134 | if ( SHOW_HELP ) 135 | { 136 | Console.WriteLine(@" 137 | ScriptSqlConfig.EXE (" + v.ToString() + @") 138 | 139 | This application generates scripts and configuration information 140 | for many SQL Server options and database objects. 141 | 142 | Required Parameters: 143 | ---------------------------------------------------------------- 144 | 145 | /server ServerName 146 | /dir OutputDirectory 147 | 148 | Optional Parameters: 149 | ---------------------------------------------------------------- 150 | 151 | /v (Verbose Output) 152 | /databases (Script databases) 153 | /noinstance (Don't script instance information) 154 | /user (SQL Server user name. It will use trusted 155 | security unless this option is specified.) 156 | /password (SQL Server password. If /user is specified then 157 | /password is required.) 158 | /scriptdb (Database to script. This will script a single 159 | database in addition to the instance scripts.) 160 | /? (Display this help) 161 | /testsmo (Test loading the SMO libraries) 162 | 163 | Sample Usage 164 | ---------------------------------------------------------------- 165 | 166 | ScriptSqlConfig.EXE /server Srv1\Instance /dir 'C:\MyDir' 167 | 168 | Notes 169 | ---------------------------------------------------------------- 170 | 1. If you have spaces in the path name, enclose it in quotes. 171 | 2. It will use trusted authentication unless both the /username 172 | and /password are specified. 173 | "); 174 | 175 | #if DEBUG 176 | Console.WriteLine(""); 177 | Console.WriteLine("Press any key to continue...."); 178 | Console.ReadLine(); 179 | #endif 180 | 181 | 182 | return; 183 | } 184 | #endregion 185 | 186 | 187 | 188 | WriteMessage("Directory: " + DIRECTORY); 189 | WriteMessage("Server: " + SERVER); 190 | 191 | /* Display the SMO version */ 192 | var ver = System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Smo.Server)).GetName().Version; 193 | WriteMessage("SMO Version: " + ver.ToString()); 194 | 195 | /* Set the SQL Server version */ 196 | SetVersions(SERVER); 197 | 198 | 199 | if (SCRIPT_INSTANCE) 200 | ScriptInstance(SERVER, DIRECTORY); 201 | 202 | if (SCRIPT_DATABASES) 203 | ScriptAllDatabases(SERVER, DIRECTORY); 204 | WriteMessage("Done."); 205 | 206 | #if DEBUG 207 | Console.WriteLine("Press any key to continue...."); 208 | Console.ReadLine(); 209 | #endif 210 | 211 | } 212 | 213 | private static void SetVersions(string server) 214 | { 215 | SqlConnection conn = GetConnection(server, "master"); 216 | Server srv = new Server(new ServerConnection(conn)); 217 | SERVER_VERSION = srv.Version; 218 | conn.Close(); 219 | 220 | Assembly a1 = System.Reflection.Assembly.GetAssembly(typeof(Microsoft.SqlServer.Management.Smo.Server)); 221 | SMO_VERSION = a1.GetName().Version; 222 | } 223 | 224 | private static void ScriptInstance(string server, string directory) 225 | { 226 | WriteMessage("Scripting Instance information..."); 227 | SqlConnection conn = GetConnection(server, "master"); 228 | Server srv = new Server(new ServerConnection(conn)); 229 | 230 | //string instanceDirectory = Path.Combine(directory, "Instance"); 231 | string instanceDirectory = directory; 232 | //DirectoryInfo d = new DirectoryInfo(instanceDirectory); 233 | //if (d.Exists) 234 | // d.Delete(true); 235 | 236 | ScriptingOptions so = new ScriptingOptions(); 237 | so.ScriptDrops = false; 238 | so.IncludeIfNotExists = true; 239 | so.ClusteredIndexes = true; 240 | so.DriAll = true; 241 | so.Indexes = true; 242 | so.SchemaQualify = true; 243 | so.Permissions = true; 244 | so.IncludeDatabaseRoleMemberships = true; 245 | so.AgentNotify = true; 246 | so.AgentAlertJob = true; 247 | 248 | 249 | if (VERBOSE) 250 | WriteMessage("Getting target server version through SMO"); 251 | 252 | //try 253 | //{ 254 | // so.TargetServerVersion = GetTargetServerVersion(srv); 255 | //} 256 | //catch (ConnectionFailureException ex) 257 | //{ 258 | // WriteMessage("Error: " + ex.Message); 259 | // if (ex.InnerException is SqlException) 260 | // { 261 | // WriteMessage("Error: " + ex.InnerException.Message); 262 | // } 263 | // System.Environment.Exit(1); 264 | //} 265 | so.Triggers = false; 266 | so.AnsiPadding = false; 267 | 268 | WriteServerProperties(srv, instanceDirectory); 269 | ScriptLogins(conn, instanceDirectory, so); 270 | ScriptDatabaseMail(srv, instanceDirectory, so); // There's a bug that it doesn't script the SMTP server and port. 271 | ScriptAgentInformation(srv, instanceDirectory, so); 272 | // ScriptAgentProxies(srv, instanceDirectory, so); 273 | ScriptLinkedServers(srv, instanceDirectory, so); 274 | ScriptServerAudits(srv, instanceDirectory, so); 275 | ScriptCredentials(srv, instanceDirectory, so); 276 | ScriptEventNotifications(conn, instanceDirectory, so); 277 | ScriptOtherObjects(srv, instanceDirectory, so); 278 | ScriptDatabaseOptions(srv, instanceDirectory, so); 279 | 280 | WriteMessage("Scripting User Objects and Security in System Databases..."); 281 | ScriptDatabase(srv.Name.ToString(), "master", Path.Combine(instanceDirectory, @"Databases\master")); 282 | ScriptDatabase(srv.Name.ToString(), "msdb", Path.Combine(instanceDirectory, @"Databases\msdb")); 283 | ScriptDatabase(srv.Name.ToString(), "model", Path.Combine(instanceDirectory, @"Databases\model")); 284 | 285 | // #if DEBUG 286 | // ScriptDatabase(srv.Name.ToString(), "AdventureWorks", Path.Combine(instanceDirectory, @"Databases\AdventureWorks")); 287 | //#endif 288 | // if we passed in a database, then script that one too. 289 | if (DATABASE.Length > 0) 290 | { 291 | ScriptDatabase(srv.Name.ToString(), DATABASE, Path.Combine(Path.Combine(instanceDirectory, @"Databases"), DATABASE)); 292 | } 293 | 294 | 295 | if (conn.State == System.Data.ConnectionState.Open) 296 | conn.Close(); 297 | } 298 | 299 | private static void ScriptEventNotifications(SqlConnection connection, string directory, ScriptingOptions so) 300 | { 301 | WriteMessage("Scripting Event Notifications..."); 302 | StringCollection script = new StringCollection(); 303 | 304 | script.Add(String.Format(@"--=============================================================================================== 305 | -- SERVER: {0} 306 | --=============================================================================================== 307 | 308 | ", connection.DataSource.ToString())); 309 | 310 | if (connection.State == System.Data.ConnectionState.Closed) 311 | connection.Open(); 312 | SqlCommand cmd = new SqlCommand(@"select se.*, sen.* 313 | from sys.server_events se 314 | join sys.server_event_notifications sen on sen.object_id = se.object_id 315 | order by type_desc;", connection); 316 | 317 | using (SqlDataReader rdr = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection)) 318 | { 319 | while (rdr.Read()) 320 | { 321 | script.Add(String.Format(@"CREATE EVENT NOTIFICATION {0} 322 | ON {1} WITH FAN_IN 323 | FOR {2} 324 | TO SERVICE '{3}', '{4}' 325 | GO 326 | 327 | ", rdr.GetString(rdr.GetOrdinal("name")), 328 | rdr.GetString(rdr.GetOrdinal("parent_class_desc")), 329 | rdr.GetString(rdr.GetOrdinal("type_desc")), 330 | rdr.GetString(rdr.GetOrdinal("service_name")), 331 | rdr.GetString(rdr.GetOrdinal("broker_instance")) 332 | )); 333 | } 334 | } 335 | 336 | WriteFile(script, Path.Combine(directory, "Event Notifications.sql"), false); 337 | } 338 | 339 | //private static SqlServerVersion GetTargetServerVersion(Server srv) 340 | //{ 341 | // string version = srv.VersionMajor.ToString() + srv.VersionMinor.ToString(); 342 | // if (version == "80") 343 | // return SqlServerVersion.Version80; 344 | 345 | // if (version == "90") 346 | // return SqlServerVersion.Version90; 347 | 348 | // if (version == "100") 349 | // return SqlServerVersion.Version100; 350 | 351 | // if (version == "1050") 352 | // return SqlServerVersion.Version105; 353 | 354 | // if (version == "110") 355 | // return SqlServerVersion.Version110; 356 | 357 | // throw new Exception("Unsupported Server Version"); 358 | 359 | //} 360 | 361 | private static void WriteServerProperties(Server smoServer, string directory) 362 | { 363 | 364 | if (VERBOSE) 365 | WriteMessage("Getting Configuration.Properties"); 366 | 367 | 368 | StringCollection settings = new StringCollection(); 369 | try 370 | { 371 | foreach (ConfigProperty p in smoServer.Configuration.Properties) 372 | { 373 | //if (VERBOSE) 374 | // WriteMessage(p.DisplayName); 375 | try 376 | { 377 | settings.Add(p.DisplayName + " [" + p.RunValue.ToString() + "]"); 378 | } 379 | catch 380 | { 381 | settings.Add(p.DisplayName + " - Failed to query property. (SMO Version conflict.)"); 382 | } 383 | 384 | } 385 | WriteFile(settings, Path.Combine(directory, "sp_configure.txt")); 386 | } 387 | catch 388 | { 389 | WriteMessage("Error: The infamous sp_ProcessorUsage bug prevents writing properties."); 390 | } 391 | 392 | if (VERBOSE) 393 | WriteMessage("Getting smoServer.Properties"); 394 | 395 | settings.Clear(); 396 | 397 | 398 | Microsoft.SqlServer.Management.Smo.Property x; 399 | for (int i = 0; i <= smoServer.Properties.Count; i++) 400 | { 401 | try 402 | { 403 | x = smoServer.Properties[i]; 404 | object propertyValue = x.Value ?? (object)"NULL"; 405 | if (x.Name != "PhysicalMemoryUsageInKB") // this value varies too much each time 406 | { 407 | settings.Add(x.Name + (" [" + propertyValue.ToString() + "]") ?? ""); 408 | } 409 | } 410 | catch (Exception) 411 | { 412 | //nothing to do, just catch the exception 413 | } 414 | } 415 | WriteFile(settings, Path.Combine(directory, "Properties.txt")); 416 | 417 | 418 | //try 419 | //{ 420 | // foreach (Microsoft.SqlServer.Management.Smo.Property x in smoServer.Properties) 421 | // { 422 | // try 423 | // { 424 | // object propertyValue = x.Value ?? (object)"NULL"; 425 | // settings.Add(x.Name + (" [" + propertyValue.ToString() + "]") ?? ""); 426 | // } 427 | // catch 428 | // { 429 | // settings.Add(x.Name + (" *** Error getting property value ***") ?? ""); 430 | // } 431 | // } 432 | // WriteFile(settings, Path.Combine(directory, "Properties.txt")); 433 | //} 434 | //catch 435 | //{ 436 | // WriteMessage("Error: The infamous sp_ProcessorUsage bug prevents writing properties."); 437 | //} 438 | 439 | } 440 | private static void ScriptLogins(SqlConnection connection, string directory, ScriptingOptions options) 441 | { 442 | 443 | 444 | 445 | WriteMessage("Scripting Logins..."); 446 | StringCollection script = new StringCollection(); 447 | 448 | StringCollection defaultDatabaseScript = new StringCollection(); 449 | 450 | #region Logins 451 | script.Add(String.Format(@"--=============================================================================================== 452 | -- SERVER: {0} 453 | --=============================================================================================== 454 | 455 | ", connection.DataSource.ToString())); 456 | 457 | if (connection.State == System.Data.ConnectionState.Closed) 458 | connection.Open(); 459 | SqlCommand cmd = new SqlCommand(@"SELECT P.[name], P.[sid] , L.[password_hash], P.[default_database_name], L.[is_expiration_checked], L.[is_policy_checked], P.[type_desc], P.[is_disabled] 460 | FROM [sys].[server_principals] P 461 | LEFT JOIN [sys].[sql_logins] L ON L.[principal_id] = P.[principal_id] 462 | where P.[type_desc] In ('WINDOWS_GROUP', 'WINDOWS_LOGIN', 'SQL_LOGIN' ) 463 | AND P.[name] not like 'BUILTIN%' 464 | and P.[NAME] not like 'NT AUTHORITY%' 465 | and P.[name] not like '%\SQLServer%' 466 | and P.[name] not like 'NT Service%' 467 | and P.[name] not in ('sa', 'guest') 468 | and P.[name] not like '##%' 469 | ORDER BY P.[name]", connection); 470 | 471 | 472 | string createLogin; 473 | using (SqlDataReader rdr = cmd.ExecuteReader()) 474 | { 475 | while (rdr.Read()) 476 | { 477 | 478 | string type_desc = rdr.GetString(rdr.GetOrdinal("type_desc")); 479 | string default_database = rdr.GetString(rdr.GetOrdinal("default_database_name")); 480 | string login_name = rdr.GetString(rdr.GetOrdinal("name")); 481 | 482 | string table_to_check_for_existance = ""; // this is either sys_logins or server_principals 483 | 484 | script.Add(String.Format(@" 485 | --------------------------------------------------------------------------------------------- 486 | -- Login: {0} 487 | --------------------------------------------------------------------------------------------- 488 | ", login_name)); 489 | switch (type_desc) 490 | { 491 | case "WINDOWS_LOGIN": 492 | case "WINDOWS_GROUP": 493 | table_to_check_for_existance = "server_principals"; 494 | 495 | createLogin = @"IF NOT EXISTS (SELECT * FROM [master].[sys].[server_principals] WHERE [name] = '" + rdr.GetString(rdr.GetOrdinal("name")) + @"') 496 | CREATE LOGIN [" + rdr.GetString(rdr.GetOrdinal("name")) + @"] FROM WINDOWS; 497 | GO 498 | 499 | "; 500 | script.Add(createLogin); 501 | 502 | if (default_database.ToLower() != "master") 503 | { 504 | string alterDatabase = string.Format(@"IF EXISTS (SELECT * FROM [master].[sys].[server_principals] WHERE [name] = '{0}') 505 | AND EXISTS (SELECT * FROM [master].[sys].[databases] WHERE [name] = '{1}') 506 | ALTER LOGIN [{0}] WITH DEFAULT_DATABASE=[{1}]; 507 | GO 508 | 509 | ", login_name, default_database); 510 | 511 | script.Add(alterDatabase); 512 | defaultDatabaseScript.Add(alterDatabase); 513 | } 514 | break; 515 | 516 | case "SQL_LOGIN": 517 | 518 | #region SQL_LOGIN 519 | 520 | table_to_check_for_existance = "sql_logins"; 521 | byte[] rawSid = new byte[85]; 522 | long length = rdr.GetBytes(rdr.GetOrdinal("sid"), 0, rawSid, 0, 85); 523 | string Sid = "0x" + BitConverter.ToString(rawSid).Replace("-", String.Empty).Substring(0, (int)length * 2); 524 | 525 | byte[] rawPasswordHash = new byte[256]; 526 | length = rdr.GetBytes(rdr.GetOrdinal("password_hash"), 0, rawPasswordHash, 0, 256); 527 | string passwordHash = "0x" + BitConverter.ToString(rawPasswordHash).Replace("-", String.Empty).Substring(0, (int)length * 2); 528 | 529 | createLogin = @"IF NOT EXISTS (SELECT * FROM [master].[sys].[sql_logins] WHERE [name] = '" + rdr.GetString(rdr.GetOrdinal("name")) + @"') 530 | CREATE LOGIN [" + rdr.GetString(rdr.GetOrdinal("name")) + @"] 531 | WITH "; 532 | if (HASHEDPASSWORDS) 533 | createLogin += @" 534 | PASSWORD = " + passwordHash + @" HASHED,"; 535 | else 536 | createLogin += @" 537 | PASSWORD = '___password___',"; 538 | createLogin += @" 539 | SID = " + Sid + @", 540 | CHECK_POLICY=OFF, 541 | "; 542 | 543 | 544 | if (rdr.GetBoolean(rdr.GetOrdinal("is_expiration_checked"))) 545 | createLogin += " CHECK_EXPIRATION = ON"; 546 | else 547 | createLogin += " CHECK_EXPIRATION = OFF"; 548 | 549 | 550 | createLogin += @" 551 | GO 552 | 553 | "; 554 | 555 | if (default_database.ToLower() != "master") 556 | { 557 | string alterDatabase = string.Format(@"IF EXISTS (SELECT * FROM [master].[sys].[sql_logins] WHERE [name] = '{0}') 558 | AND EXISTS (SELECT * FROM [master].[sys].[databases] WHERE [name] = '{1}') 559 | ALTER LOGIN [{0}] WITH DEFAULT_DATABASE=[{1}]; 560 | GO 561 | 562 | ", login_name, default_database); 563 | 564 | createLogin += alterDatabase; 565 | defaultDatabaseScript.Add(alterDatabase); 566 | } 567 | 568 | createLogin += @"IF EXISTS (SELECT * FROM [master].[sys].[sql_logins] WHERE [name] = '" + rdr.GetString(rdr.GetOrdinal("name")) + @"') 569 | ALTER LOGIN [" + rdr.GetString(rdr.GetOrdinal("name")) + @"] WITH "; 570 | if (rdr.GetBoolean(rdr.GetOrdinal("is_expiration_checked"))) 571 | createLogin += "CHECK_EXPIRATION = ON"; 572 | else 573 | createLogin += "CHECK_EXPIRATION = OFF"; 574 | 575 | createLogin += ", "; 576 | 577 | if (rdr.GetBoolean(rdr.GetOrdinal("is_policy_checked"))) 578 | createLogin += "CHECK_POLICY = ON"; 579 | else 580 | createLogin += "CHECK_POLICY = OFF"; 581 | 582 | 583 | createLogin += @" 584 | GO 585 | 586 | "; 587 | 588 | script.Add(createLogin); 589 | #endregion 590 | break; // SQL Login 591 | 592 | default: 593 | WriteMessage(String.Format("Unknown Loging Type [{0}] for [{1}]", type_desc, login_name)); 594 | break; 595 | 596 | } 597 | 598 | if (rdr.GetBoolean(rdr.GetOrdinal("is_disabled")) == true) 599 | { 600 | script.Add(string.Format(@"IF EXISTS (SELECT * FROM [master].[sys].[{0}] WHERE [name] = '{1}') 601 | ALTER LOGIN [{1}] DISABLE 602 | GO 603 | 604 | ", table_to_check_for_existance, login_name)); 605 | } 606 | 607 | #region Group Membership 608 | 609 | SqlConnection groupConn = new SqlConnection(connection.ConnectionString); 610 | 611 | SqlCommand groupCmd = new SqlCommand(@"select l.[name] AS LoginName, r.name AS RoleName 612 | from [master].[sys].[server_role_members] rm 613 | join [master].[sys].[server_principals] r on r.[principal_id] = rm.[role_principal_id] 614 | join [master].[sys].[server_principals] l on l.[principal_id] = rm.[member_principal_id] 615 | where l.[name] = @login_name 616 | /* AND l.[name] not in ('sa') 617 | AND l.[name] not like 'BUILTIN%' 618 | and l.[NAME] not like 'NT AUTHORITY%' 619 | and l.[name] not like '%\SQLServer%' 620 | and l.[name] not like '##%' 621 | and l.[name] not like 'NT Service%' */ 622 | ORDER BY r.[name], l.[name]", groupConn); 623 | 624 | groupCmd.Parameters.Add("login_name", System.Data.SqlDbType.NVarChar, 128).Value = login_name; 625 | 626 | groupConn.Open(); 627 | using (SqlDataReader groupReader = groupCmd.ExecuteReader()) 628 | { 629 | if (groupReader.HasRows) 630 | { 631 | 632 | while (groupReader.Read()) 633 | { 634 | script.Add(String.Format(@"IF EXISTS (SELECT * FROM [master].[sys].[{2}] WHERE [name] = '{0}') 635 | EXEC sp_addsrvrolemember @loginame = N'{0}', @rolename = N'{1}' 636 | GO 637 | 638 | ", login_name, groupReader.GetString(groupReader.GetOrdinal("RoleName")), table_to_check_for_existance)); 639 | } 640 | } 641 | groupReader.Close(); 642 | groupConn.Close(); 643 | } 644 | #endregion 645 | 646 | 647 | 648 | } 649 | rdr.Close(); 650 | } 651 | #endregion 652 | 653 | #region Default Databases 654 | 655 | // add the default databases a second time so it can be run separately 656 | script.Add(@"------------------------------------------------------------------------------------- 657 | -- Default databases are repeated so you can rerun them after mirrors are failed over 658 | -------------------------------------------------------------------------------------"); 659 | 660 | if (defaultDatabaseScript.Count > 0) 661 | { 662 | script.Append(defaultDatabaseScript); 663 | } 664 | #endregion 665 | 666 | 667 | WriteFile(script, Path.Combine(directory, "Logins.sql")); 668 | } 669 | 670 | private static void ScriptDatabaseMail(Server smoServer, string directory, ScriptingOptions options) 671 | { 672 | // if this is express, then don't script database mail 673 | if (smoServer.EngineEdition == Edition.Express) 674 | return; 675 | 676 | 677 | WriteMessage("Scripting Database Mail..."); 678 | StringCollection script = new StringCollection(); 679 | 680 | // Script the configuration values 681 | foreach (ConfigurationValue cv in smoServer.Mail.ConfigurationValues) 682 | { 683 | script.Append(cv.Script(options)); 684 | } 685 | script.Add("GO" + Environment.NewLine); 686 | 687 | // Script the accounts 688 | foreach (MailAccount ma in smoServer.Mail.Accounts) 689 | { 690 | script.Append(ma.Script(options)); 691 | script.Add("GO" + Environment.NewLine); 692 | 693 | // script the SMTP server for each account 694 | foreach (MailServer ms in ma.MailServers) 695 | { 696 | script.Append(ms.Script(options)); 697 | script.Add("GO" + Environment.NewLine); 698 | } 699 | } 700 | 701 | // Script each profile 702 | foreach (MailProfile mp in smoServer.Mail.Profiles) 703 | { 704 | script.Append(mp.Script(options)); 705 | script.Add("END"); // SMO doesn't script the END statement 706 | script.Add("GO" + Environment.NewLine); 707 | } 708 | 709 | WriteFile(script, Path.Combine(directory, "DatabaseMail.sql")); 710 | } 711 | 712 | private static void ScriptIndividualJobs(Server smoServer, string directory, ScriptingOptions options) 713 | { 714 | 715 | 716 | // delete any remaining job files 717 | 718 | string workingDirectory = Path.Combine(directory, "SQL Server Agent"); 719 | workingDirectory = Path.Combine(workingDirectory, "SQL Server Agent Jobs"); 720 | 721 | // Get a list of files in the directory 722 | StringCollection files = new StringCollection(); 723 | if (Directory.Exists(workingDirectory)) 724 | files.AddRange(Directory.GetFiles(workingDirectory)); 725 | 726 | // Script each job, save it, and delete the entry for the job 727 | foreach (Job j in smoServer.JobServer.Jobs) 728 | { 729 | string jobName = CleanUpFileName(j.Name); 730 | StringCollection script = j.Script(options); 731 | string fileName = Path.Combine(workingDirectory, jobName + ".sql"); 732 | WriteFile(script, fileName); 733 | 734 | // remove this file name from our list of files 735 | files.Remove(fileName); 736 | } 737 | 738 | // Remove any files that are left 739 | foreach (string extraFile in files) 740 | { 741 | File.Delete(extraFile); 742 | } 743 | 744 | 745 | } 746 | 747 | private static void MoveFile(string fileName, string sourcePath, string destinationPath) 748 | { 749 | if (!File.Exists(Path.Combine(sourcePath, fileName))) 750 | return; 751 | 752 | if (File.Exists(Path.Combine(destinationPath, fileName))) 753 | return; 754 | 755 | File.Move(Path.Combine(sourcePath, fileName), Path.Combine(destinationPath, fileName)); 756 | } 757 | private static void ScriptAgentInformation(Server smoServer, string directory, ScriptingOptions options) 758 | { 759 | if (smoServer.EngineEdition != Edition.Express) 760 | { 761 | 762 | //TODO: Add job categories 763 | 764 | WriteMessage("Scripting Agent Information..."); 765 | 766 | // Move any existing files to their new location (added in 2012.4) 767 | // Previously they were scripted in the main server directory) 768 | string agentDirectory = Path.Combine(directory, "SQL Server Agent"); 769 | 770 | // Create the directory 771 | if (!Directory.Exists(agentDirectory)) 772 | Directory.CreateDirectory(agentDirectory); 773 | 774 | 775 | MoveFile("SQL Server Agent Jobs.sql", directory, agentDirectory); 776 | MoveFile("SQL Server Agent Operators.sql", directory, agentDirectory); 777 | MoveFile("SQL Server Agent Alerts.sql", directory, agentDirectory); 778 | MoveFile("SQL Server Agent Properties.txt", directory, agentDirectory); 779 | MoveFile("SQL Server Agent Proxy Accounts.sql", directory, agentDirectory); 780 | 781 | StringCollection script = smoServer.JobServer.Jobs.Script(options); 782 | WriteFile(script, Path.Combine(agentDirectory,"SQL Server Agent Jobs.sql"), true); 783 | 784 | ScriptIndividualJobs(smoServer, directory, options); 785 | 786 | script = smoServer.JobServer.Operators.Script(options); 787 | WriteFile(script, Path.Combine(agentDirectory, "SQL Server Agent Operators.sql")); 788 | 789 | script = smoServer.JobServer.AlertSystem.Script(options); 790 | script.Append(smoServer.JobServer.Alerts.Script(options)); 791 | script.Insert(0, @"------------------------------------------------------------------ 792 | -- NOTE: Alert notifications aren't scripted in this release. 793 | ------------------------------------------------------------------ 794 | 795 | "); 796 | 797 | WriteFile(script, Path.Combine(agentDirectory, "SQL Server Agent Alerts.sql")); 798 | 799 | script = new StringCollection(); 800 | SqlPropertyCollection p = smoServer.JobServer.Properties; 801 | foreach (Microsoft.SqlServer.Management.Smo.Property i in p) 802 | { 803 | //Console.WriteLine("Name: {0}", i.Name.ToString()); 804 | script.Add(i.Name.ToString() + " [" + i.Value.ToString() + "]"); 805 | } 806 | 807 | WriteFile(script, Path.Combine(agentDirectory, "SQL Server Agent Properties.txt")); 808 | 809 | // Script Proxies 810 | script = new StringCollection(); 811 | foreach (ProxyAccount a in smoServer.JobServer.ProxyAccounts) 812 | { 813 | script.Append(a.Script(options)); 814 | } 815 | WriteFile(script, Path.Combine(agentDirectory, "SQL Server Agent Proxy Accounts.sql")); 816 | 817 | //script = smoServer.Mail.Script(); 818 | } 819 | 820 | } 821 | 822 | //private static void ScriptAgentProxies(Server smoServer, string directory, ScriptingOptions options) 823 | //{ 824 | // if (smoServer.EngineEdition != Edition.Express) 825 | // { 826 | // WriteMessage("Scripting Agent Proxies..."); 827 | // StringCollection script = new StringCollection(); 828 | // string proxyDirectory = Path.Combine(directory, "SQL Server Agent"); 829 | // //proxyDirectory = Path.Combine(proxyDirectory, "Proxies"); 830 | 831 | // //RemoveSqlFiles(proxyDirectory); 832 | 833 | // foreach (Microsoft.SqlServer.Management.Smo.Agent.ProxyAccount proxy in smoServer.JobServer.ProxyAccounts) 834 | // { 835 | // script.(proxy.Script().); 836 | // WriteFile(script, Path.Combine(proxyDirectory, proxy.Name + ".sql")); 837 | // } 838 | // //script = smoServer.Mail.Script(); 839 | // } 840 | 841 | //} 842 | 843 | private static void ScriptLinkedServers(Server smoServer, string directory, ScriptingOptions options) 844 | { 845 | WriteMessage("Scripting Linked Servers..."); 846 | StringCollection script = new StringCollection(); 847 | 848 | //string linkedServerDirectory = Path.Combine(directory, "Linked Servers"); 849 | //RemoveSqlFiles(linkedServerDirectory); 850 | 851 | foreach (LinkedServer linkedServer in smoServer.LinkedServers) 852 | { 853 | script.Append(linkedServer.Script(options)); 854 | 855 | //string serverName = linkedServer.Name.Replace(@"\", "_"); 856 | WriteFile(script, Path.Combine(directory, "Linked Servers.sql"), true); 857 | } 858 | } 859 | 860 | private static void ScriptServerAudits(Server smoServer, string directory, ScriptingOptions options) 861 | { 862 | 863 | if (smoServer.VersionMajor >= 10) 864 | { 865 | WriteMessage("Scripting Audits..."); 866 | StringCollection script = new StringCollection(); 867 | 868 | foreach (Audit audit in smoServer.Audits) 869 | { 870 | script.Append(audit.Script(options)); 871 | 872 | } 873 | 874 | foreach (ServerAuditSpecification serverAudit in smoServer.ServerAuditSpecifications) 875 | { 876 | script.Append(serverAudit.Script(options)); 877 | 878 | } 879 | 880 | 881 | foreach (Audit audit in smoServer.Audits) 882 | { 883 | if (audit.Enabled) 884 | { 885 | string enableStatement = String.Format(@"ALTER SERVER AUDIT [{0}] WITH (STATE = ON); 886 | ", audit.Name); 887 | script.Add(enableStatement); 888 | 889 | } 890 | 891 | } 892 | 893 | 894 | WriteFile(script, Path.Combine(directory, "Audits.sql"), true); 895 | } 896 | 897 | } 898 | 899 | private static void ScriptOtherObjects(Server smoServer, string directory, ScriptingOptions options) 900 | { 901 | options.Permissions = true; 902 | 903 | WriteMessage("Scripting Endpoints..."); 904 | StringCollection script = new StringCollection(); 905 | foreach (Endpoint endPoint in smoServer.Endpoints) 906 | { 907 | if (endPoint.IsSystemObject == false) 908 | { 909 | script.Append(endPoint.Script(options)); 910 | } 911 | } 912 | 913 | WriteFile(script, Path.Combine(directory, "Endpoints.sql"), true); 914 | 915 | 916 | } 917 | 918 | private static void ScriptDatabaseOptions(Server smoServer, string directory, ScriptingOptions options) 919 | { 920 | WriteMessage("Scripting Database Options..."); 921 | StringCollection script = new StringCollection(); 922 | script.Add(string.Format("-- Server: {0}", smoServer.Name)); 923 | foreach (Database db in smoServer.Databases) 924 | { 925 | if (db.IsAccessible && db.Name != "tempdb" && !db.IsDatabaseSnapshot && !db.IsSystemObject) 926 | { 927 | // Script the database owner 928 | script.Add("------------------------------------------------------------------------"); 929 | string sql = String.Format(@"USE [{0}] 930 | GO 931 | EXEC dbo.sp_changedbowner @loginame = N'{1}', @map = false 932 | GO 933 | 934 | ", db.Name, db.Owner); 935 | 936 | script.Add(sql); 937 | 938 | // Script the trustworthy setting 939 | if (db.Trustworthy) 940 | { 941 | sql = string.Format("ALTER DATABASE [{0}] SET TRUSTWORTHY ON;\r\nGO\r\n", db.Name); 942 | script.Add(sql); 943 | } 944 | else 945 | { 946 | sql = string.Format("ALTER DATABASE [{0}] SET TRUSTWORTHY OFF;\r\nGO\r\n", db.Name); 947 | script.Add(sql); 948 | } 949 | 950 | } 951 | } 952 | 953 | WriteFile(script, Path.Combine(directory, "Database Options.sql"), false); 954 | } 955 | 956 | 957 | private static void ScriptCredentials(Server smoServer, string directory, ScriptingOptions options) 958 | { 959 | //smoServer.ev 960 | WriteMessage("Scripting Credentials..."); 961 | StringCollection script = new StringCollection(); 962 | script.Add(GetHeaderCommentBlock("Credentials")); 963 | foreach (Credential cred in smoServer.Credentials) 964 | { 965 | 966 | script.Add(String.Format(@"IF NOT EXISTS(select * from master.sys.credentials where [name] = '{0}') 967 | CREATE CREDENTIAL [{0}] WITH IDENTITY = N'{1}', SECRET = N'___password___'", cred.Name, cred.Identity)); 968 | } 969 | 970 | WriteFile(script, Path.Combine(directory, "Credentials.sql"), true); 971 | } 972 | 973 | 974 | 975 | 976 | private static void ScriptAllDatabases(string server, string directory) 977 | { 978 | WriteMessage("Scripting databases..."); 979 | SqlConnection conn = GetConnection(server, "master"); 980 | Server srv = new Server(new ServerConnection(conn)); 981 | string databasesDirectory = Path.Combine(directory, "Databases"); 982 | 983 | 984 | 985 | foreach (Database db in srv.Databases) 986 | { 987 | if (db.IsAccessible && db.Name != "tempdb" && !db.IsDatabaseSnapshot) 988 | { 989 | WriteMessage("Scripting Database: " + db.Name); 990 | // WriteMessage(db.IsSystemObject.ToString()); 991 | string outputDirectory = Path.Combine(databasesDirectory, db.Name); 992 | DirectoryInfo d = new DirectoryInfo(outputDirectory); 993 | //if (d.Exists) 994 | // d.Delete(true); 995 | 996 | ScriptDatabase(server, db.Name, outputDirectory); 997 | } 998 | else 999 | { 1000 | WriteMessage("Skipping Database: " + db.Name); 1001 | } 1002 | } 1003 | } 1004 | 1005 | public static void WriteMessage(string message) 1006 | { 1007 | string output = message; 1008 | if (VERBOSE) 1009 | output = DateTime.Now.ToString() + " : " + output; 1010 | Console.WriteLine(output); 1011 | } 1012 | 1013 | 1014 | 1015 | private static void ScriptDatabase(string server, string database, string directory) 1016 | { 1017 | 1018 | string[] defaultFields = new string[2] { "IsSystemObject", "IsEncrypted" }; 1019 | ScriptingOptions so = new ScriptingOptions(); 1020 | so.ScriptDrops = false; 1021 | so.IncludeIfNotExists = true; 1022 | so.ClusteredIndexes = true; 1023 | so.DriAll = true; 1024 | so.Indexes = true; 1025 | so.SchemaQualify = true; 1026 | 1027 | so.Triggers = false; 1028 | so.AnsiPadding = false; 1029 | so.Permissions = true; 1030 | so.IncludeDatabaseRoleMemberships = true; 1031 | 1032 | SqlConnection conn = GetConnection(server, database); 1033 | Server srv = new Server(new ServerConnection(conn)); 1034 | 1035 | // so.TargetServerVersion = GetTargetServerVersion(srv); 1036 | // Console.WriteLine(so.TargetServerVersion); 1037 | Database db = srv.Databases[database]; 1038 | 1039 | srv.SetDefaultInitFields(typeof(StoredProcedure), "IsSystemObject"); 1040 | string objectDir; 1041 | 1042 | // Script the database properties - on hold 1043 | //StringCollection databaseProperties = db.Script(so); 1044 | //WriteFile(databaseProperties, Path.Combine(directory, "T1.txt"), true); 1045 | //foreach (Property p in db.Properties) 1046 | //{ Console.WriteLine("{0} - {1}", p.Name, p.Value); } 1047 | 1048 | 1049 | #region Tables 1050 | objectDir = Path.Combine(directory, "Tables"); 1051 | 1052 | srv.SetDefaultInitFields(typeof(Table), new string[1] { "IsSystemObject" }); 1053 | 1054 | StringCollection nonClusteredIndexes = new StringCollection(); 1055 | 1056 | // put one use database at the top 1057 | nonClusteredIndexes.Add(@"USE [" + database + "]" + Environment.NewLine); 1058 | 1059 | 1060 | foreach (Table t in db.Tables) 1061 | { 1062 | 1063 | if (!t.IsSystemObject) 1064 | { 1065 | if ( VERBOSE ) 1066 | WriteMessage("Table: " + t.Name); 1067 | StringCollection sc = t.Script(so); 1068 | 1069 | // Script any triggers. Encrypted triggers are listed but not scripted. 1070 | if (t.Triggers.Count > 0) 1071 | { 1072 | foreach (Trigger trg in t.Triggers) 1073 | { 1074 | if (!trg.IsEncrypted) 1075 | { 1076 | StringCollection triggerScript = trg.Script(so); 1077 | sc.Append(triggerScript); 1078 | } 1079 | else 1080 | { 1081 | string encryptedTrigger = String.Format("-- Trigger {0} is encrypted and can't be scripted.", trg.Name); 1082 | sc.Add(encryptedTrigger); 1083 | } 1084 | } 1085 | } 1086 | 1087 | // Script all non-clustered indexes to a separate file 1088 | if (t.HasIndex) 1089 | { 1090 | foreach (Index idx in t.Indexes) 1091 | { 1092 | if (!idx.IsClustered) 1093 | { 1094 | nonClusteredIndexes.Append(idx.Script(so)); 1095 | } 1096 | } 1097 | } 1098 | string fileName = Path.Combine(objectDir, CleanUpFileName(t.Schema) + "." + CleanUpFileName(t.Name) + ".sql"); 1099 | 1100 | WriteFile(sc, fileName, true); 1101 | } 1102 | } 1103 | 1104 | // Write the non-clustered indexes file 1105 | WriteFile(nonClusteredIndexes, Path.Combine(directory, "Non-Clustered Indexes.sql"), true); 1106 | #endregion 1107 | 1108 | 1109 | 1110 | 1111 | #region Stored Procedures 1112 | objectDir = Path.Combine(directory, "Sprocs"); 1113 | 1114 | srv.SetDefaultInitFields(typeof(StoredProcedure), defaultFields); 1115 | foreach (StoredProcedure sp in db.StoredProcedures) 1116 | { 1117 | if (!sp.IsSystemObject && !sp.IsEncrypted) 1118 | { 1119 | if (VERBOSE) 1120 | WriteMessage("Sproc: " + sp.Name); 1121 | StringCollection sc = sp.Script(so); 1122 | 1123 | string fileName = Path.Combine(objectDir, CleanUpFileName(sp.Schema) + "." + CleanUpFileName(sp.Name) + ".sql"); 1124 | 1125 | WriteFile(sc, fileName, true); 1126 | } 1127 | 1128 | } 1129 | 1130 | 1131 | #endregion 1132 | 1133 | #region User-defined data types 1134 | objectDir = Path.Combine(directory, "DataTypes"); 1135 | 1136 | 1137 | foreach (UserDefinedDataType udt in db.UserDefinedDataTypes) 1138 | { 1139 | 1140 | if (VERBOSE) 1141 | WriteMessage("DataType: " + udt.Name); 1142 | StringCollection sc = udt.Script(so); 1143 | string fileName = Path.Combine(objectDir, CleanUpFileName(udt.Schema) + "." + CleanUpFileName(udt.Name) + ".sql"); 1144 | 1145 | WriteFile(sc, fileName, true); 1146 | 1147 | } 1148 | #endregion 1149 | 1150 | #region Views 1151 | objectDir = Path.Combine(directory, "Views"); 1152 | 1153 | srv.SetDefaultInitFields(typeof(View), defaultFields); 1154 | foreach (View v in db.Views) 1155 | { 1156 | if (!v.IsSystemObject && !v.IsEncrypted) 1157 | { 1158 | if (VERBOSE) 1159 | WriteMessage("View: " + v.Name); 1160 | StringCollection sc = v.Script(so); 1161 | string fileName = Path.Combine(objectDir, CleanUpFileName(v.Schema) + "." + CleanUpFileName(v.Name) + ".sql"); 1162 | 1163 | WriteFile(sc, fileName, true); 1164 | } 1165 | } 1166 | #endregion 1167 | 1168 | #region Triggers 1169 | objectDir = Path.Combine(directory, "DDLTriggers"); 1170 | 1171 | srv.SetDefaultInitFields(typeof(Trigger), defaultFields); 1172 | foreach (DatabaseDdlTrigger tr in db.Triggers) 1173 | { 1174 | if (!tr.IsSystemObject && !tr.IsEncrypted) 1175 | { 1176 | if (VERBOSE) 1177 | WriteMessage("DDL Trigger: " + tr.Name); 1178 | StringCollection sc = tr.Script(so); 1179 | string fileName = Path.Combine(objectDir, CleanUpFileName(tr.Name) + ".sql"); 1180 | 1181 | WriteFile(sc, fileName, true); 1182 | } 1183 | } 1184 | #endregion 1185 | 1186 | 1187 | #region Table Types 1188 | if (srv.VersionMajor >= 10) 1189 | { 1190 | objectDir = Path.Combine(directory, "TableTypes"); 1191 | 1192 | 1193 | foreach (UserDefinedTableType tt in db.UserDefinedTableTypes) 1194 | { 1195 | 1196 | if (VERBOSE) 1197 | WriteMessage("TableType: " + tt.Name); 1198 | StringCollection sc = tt.Script(so); 1199 | string fileName = Path.Combine(objectDir, CleanUpFileName(tt.Name) + ".sql"); 1200 | 1201 | WriteFile(sc, fileName, true); 1202 | 1203 | } 1204 | } 1205 | #endregion 1206 | 1207 | #region Assemblies 1208 | //objectDir = Path.Combine(directory, "Assemblies"); 1209 | //RemoveSqlFiles(objectDir); 1210 | //foreach (SqlAssembly asm in db.Assemblies) 1211 | //{ 1212 | // if (!asm.IsSystemObject) 1213 | // { 1214 | // WriteMessage("Assembly: " + asm.Name); 1215 | // StringCollection sc = asm.Script(so); 1216 | // string fileName = Path.Combine(objectDir,asm.Name + ".sql"); 1217 | 1218 | // WriteFile(sc, fileName); 1219 | // } 1220 | //} 1221 | #endregion 1222 | 1223 | #region User-Defined Functions 1224 | objectDir = Path.Combine(directory, "UDF"); 1225 | 1226 | srv.SetDefaultInitFields(typeof(UserDefinedFunction), defaultFields); 1227 | foreach (UserDefinedFunction udf in db.UserDefinedFunctions) 1228 | { 1229 | if (!udf.IsSystemObject && !udf.IsEncrypted) 1230 | { 1231 | if (VERBOSE) 1232 | WriteMessage("UDF: " + udf.Name); 1233 | StringCollection sc = udf.Script(so); 1234 | string fileName = Path.Combine(objectDir, CleanUpFileName(udf.Schema) + "." + CleanUpFileName(udf.Name) + ".sql"); 1235 | 1236 | WriteFile(sc, fileName, true); 1237 | } 1238 | } 1239 | #endregion 1240 | 1241 | 1242 | 1243 | #region Users 1244 | ScriptDatabaseUsers(db, directory, so); 1245 | ScriptDatabaseRoles(db, directory, so); 1246 | // ScriptPermissions(db, directory, so); 1247 | #endregion 1248 | 1249 | ScriptServiceBroker(db, directory, so); 1250 | 1251 | ScriptSchemas(db, directory, so); 1252 | 1253 | } 1254 | 1255 | private static void ScriptSchemas(Database db, string directory, ScriptingOptions so) 1256 | { 1257 | so.Permissions = true; 1258 | 1259 | StringCollection sc = new StringCollection(); 1260 | sc.Add(GetHeaderCommentBlock("Schemas")); 1261 | foreach (Microsoft.SqlServer.Management.Smo.Schema schema in db.Schemas) 1262 | { 1263 | // SQL Server 2008 SMO is having trouble scripting schema objects - IsSystemObject is failing 1264 | //if (SMO_VERSION.Major >= 11) 1265 | //{ 1266 | // if (1==1 /*!schema.IsSystemObject */) 1267 | // { 1268 | // if (VERBOSE) 1269 | // WriteMessage("Schema: " + schema.Name); 1270 | // sc.Append(schema.Script(so)); 1271 | // } 1272 | 1273 | //} 1274 | //else 1275 | //{ 1276 | if (VERBOSE) 1277 | WriteMessage("Schema: " + schema.Name); 1278 | sc.Append(schema.Script(so)); 1279 | //} 1280 | 1281 | 1282 | } 1283 | 1284 | string fileName = Path.Combine(directory, "Schemas.sql"); 1285 | WriteFile(sc, fileName, true); 1286 | } 1287 | #region ScriptPermissions 1288 | // This code is commented out until I get more time to work on it. 1289 | //private static void ScriptPermissions(Database db, string directory, ScriptingOptions so) 1290 | //{ 1291 | // if (VERBOSE) 1292 | // WriteMessage("Permissions..."); 1293 | 1294 | // StringCollection sc = new StringCollection(); 1295 | // DatabasePermissionInfo[] dbPerms = db.EnumDatabasePermissions(); 1296 | // foreach (DatabasePermissionInfo i in dbPerms) 1297 | // { 1298 | 1299 | 1300 | // // WriteMessage(i.ToString()); 1301 | // } 1302 | 1303 | // System.Data.DataTable objects = db.EnumObjects(); 1304 | // foreach (System.Data.DataRow r in objects.Rows) 1305 | // { 1306 | // WriteMessage(r["Name"].ToString()); 1307 | // } 1308 | 1309 | // ObjectPermissionInfo[] objPerms = db.EnumObjectPermissions(); 1310 | // foreach (ObjectPermissionInfo o in objPerms) 1311 | // { 1312 | // //WriteMessage(o.ToString()); 1313 | // } 1314 | 1315 | //} 1316 | #endregion 1317 | 1318 | 1319 | private static void ScriptServiceBroker(Database db, string directory, ScriptingOptions so) 1320 | { 1321 | so.Permissions = true; 1322 | 1323 | #region Routes 1324 | StringCollection sc = new StringCollection(); 1325 | sc.Add(GetHeaderCommentBlock("Routes")); 1326 | foreach (Microsoft.SqlServer.Management.Smo.Broker.ServiceRoute route in db.ServiceBroker.Routes) 1327 | { 1328 | if (VERBOSE) 1329 | WriteMessage("Route: " + route.Name); 1330 | 1331 | sc.Append(route.Script(so)); 1332 | 1333 | } 1334 | #endregion 1335 | 1336 | #region Message Types 1337 | sc.Add(GetHeaderCommentBlock("Message Types")); 1338 | 1339 | foreach (Microsoft.SqlServer.Management.Smo.Broker.MessageType messageType in db.ServiceBroker.MessageTypes) 1340 | { 1341 | if (!messageType.IsSystemObject) 1342 | sc.Append(messageType.Script(so)); 1343 | } 1344 | #endregion 1345 | 1346 | #region Queues 1347 | sc.Add(GetHeaderCommentBlock("Queues")); 1348 | 1349 | foreach (Microsoft.SqlServer.Management.Smo.Broker.ServiceQueue queue in db.ServiceBroker.Queues) 1350 | { 1351 | if (!queue.IsSystemObject) 1352 | sc.Append(queue.Script(so)); 1353 | } 1354 | #endregion 1355 | 1356 | #region Contracts 1357 | sc.Add(GetHeaderCommentBlock("Contracts")); 1358 | 1359 | foreach (Microsoft.SqlServer.Management.Smo.Broker.ServiceContract contract in db.ServiceBroker.ServiceContracts) 1360 | { 1361 | if (!contract.IsSystemObject) 1362 | sc.Append(contract.Script(so)); 1363 | } 1364 | #endregion 1365 | 1366 | #region Services 1367 | sc.Add(GetHeaderCommentBlock("Services")); 1368 | 1369 | foreach (Microsoft.SqlServer.Management.Smo.Broker.BrokerService service in db.ServiceBroker.Services) 1370 | { 1371 | if (!service.IsSystemObject) 1372 | sc.Append(service.Script(so)); 1373 | } 1374 | #endregion 1375 | 1376 | string fileName = Path.Combine(directory, "Service Broker.sql"); 1377 | WriteFile(sc, fileName, true); 1378 | 1379 | } 1380 | 1381 | private static void ScriptDatabaseUsers(Database db, string directory, ScriptingOptions so) 1382 | { 1383 | StringCollection sc = new StringCollection(); 1384 | foreach (User u in db.Users) 1385 | { 1386 | 1387 | if (!u.IsSystemObject) 1388 | { 1389 | if (VERBOSE) 1390 | WriteMessage("User: " + u.Name); 1391 | //u.Script(so). 1392 | StringCollection thisScript = u.Script(so); 1393 | sc.Append(thisScript); 1394 | 1395 | } 1396 | } 1397 | string fileName = Path.Combine(directory, "Users.sql"); 1398 | WriteFile(sc, fileName, true); 1399 | } 1400 | 1401 | 1402 | private static void ScriptDatabaseRoles(Database db, string directory, ScriptingOptions so) 1403 | { 1404 | if (VERBOSE) 1405 | WriteMessage("Roles..."); 1406 | 1407 | StringCollection sc = new StringCollection(); 1408 | foreach (DatabaseRole r in db.Roles) 1409 | { 1410 | if (!r.IsFixedRole) 1411 | { 1412 | // so. 1413 | StringCollection thisScript = r.Script(so); 1414 | foreach (string s in thisScript) 1415 | sc.Append(thisScript); 1416 | } 1417 | 1418 | 1419 | } 1420 | 1421 | string fileName = Path.Combine(directory, "Database Roles.sql"); 1422 | WriteFile(sc, fileName, true); 1423 | 1424 | // Application roles----------------------------------------------------------------- 1425 | sc = new StringCollection(); 1426 | foreach (ApplicationRole ar in db.ApplicationRoles) 1427 | { 1428 | StringCollection thisScript = ar.Script(so); 1429 | sc.Append(thisScript); 1430 | } 1431 | 1432 | fileName = Path.Combine(directory, "Application Roles.sql"); 1433 | WriteFile(sc, fileName, true); 1434 | 1435 | } 1436 | 1437 | private static void RemoveSqlFiles(string directory) 1438 | { 1439 | throw new NotImplementedException("RemoveSqlFiles shouldn't be called."); 1440 | //DirectoryInfo dir = new DirectoryInfo(directory); 1441 | //if (dir.Exists) 1442 | //{ 1443 | // foreach (FileInfo f in dir.GetFiles("*.sql")) 1444 | // f.Delete(); 1445 | //} 1446 | } 1447 | 1448 | private static void WriteFile(StringCollection script, string fileName) 1449 | { 1450 | WriteFile(script, fileName, false); 1451 | } 1452 | private static void WriteFile(StringCollection script, string fileName, bool addGoStatements) 1453 | { 1454 | // Clean up an invalid characters 1455 | // pull apart the file passed in and remove any funky characters 1456 | string directory = Path.GetDirectoryName(fileName); 1457 | string fileOnly = Path.GetFileName(fileName); 1458 | 1459 | fileOnly = CleanUpFileName(fileOnly); 1460 | 1461 | fileName = Path.Combine(directory, fileOnly); 1462 | 1463 | //if (File.Exists(fileName)) 1464 | // File.Delete(fileName); 1465 | 1466 | 1467 | if (!Directory.Exists(directory)) 1468 | Directory.CreateDirectory(directory); 1469 | 1470 | TextWriter tw = new StreamWriter(fileName); 1471 | foreach (string s in script) 1472 | { 1473 | string stringToWrite = s; 1474 | if (!stringToWrite.EndsWith(Environment.NewLine)) 1475 | stringToWrite += Environment.NewLine; 1476 | 1477 | tw.Write(stringToWrite); 1478 | //if (s.StartsWith("SET QUOTED_IDENTIFIER") || s.StartsWith("SET ANSI_NULLS")) 1479 | // tw.Write(System.Environment.NewLine + "GO"); 1480 | 1481 | if (addGoStatements) 1482 | { 1483 | // if the previous line doesn't endwith a NewLine, write one 1484 | if (!stringToWrite.EndsWith(System.Environment.NewLine)) 1485 | tw.Write(System.Environment.NewLine); 1486 | 1487 | tw.WriteLine("GO" + System.Environment.NewLine); 1488 | } 1489 | } 1490 | 1491 | tw.Close(); 1492 | return; 1493 | } 1494 | 1495 | private static string CleanUpFileName(string fileOnly) 1496 | { 1497 | // remove any \ and replace with an under score. 1498 | // this is mainly done for schemas and objects owned by DOMAIN\User 1499 | fileOnly = fileOnly.Replace(@"\", "_"); 1500 | 1501 | // remove any of the characters that are invalid 1502 | string regexSearch = new string(Path.GetInvalidFileNameChars()); 1503 | Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); 1504 | fileOnly = r.Replace(fileOnly, ""); 1505 | 1506 | return fileOnly; 1507 | } 1508 | 1509 | private static SqlConnection GetConnection(string serverName, string databaseName) 1510 | { 1511 | SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(); 1512 | csb.DataSource = serverName; 1513 | 1514 | 1515 | if (USER_NAME.Length > 0) 1516 | { 1517 | csb.IntegratedSecurity = false; 1518 | csb.UserID = USER_NAME; 1519 | csb.Password = PASSWORD; 1520 | } 1521 | else 1522 | { 1523 | csb.IntegratedSecurity = true; 1524 | } 1525 | 1526 | 1527 | 1528 | csb.InitialCatalog = databaseName; 1529 | csb.ApplicationName = "ScriptSqlConfig"; 1530 | SqlConnection c = new SqlConnection(csb.ConnectionString); 1531 | return c; 1532 | 1533 | } 1534 | 1535 | 1536 | private static string GetHeaderCommentBlock(string title) 1537 | { 1538 | return string.Format(@" 1539 | --------------------------------------------- 1540 | -- {0} 1541 | --------------------------------------------- 1542 | ", title); 1543 | 1544 | } 1545 | } 1546 | } 1547 | --------------------------------------------------------------------------------