diff --git a/Sources/SqlDatabase.PowerShell.Internal/CmdLetExecutor.cs b/Sources/SqlDatabase.PowerShell.Internal/CmdLetExecutor.cs new file mode 100644 index 00000000..7954d0ab --- /dev/null +++ b/Sources/SqlDatabase.PowerShell.Internal/CmdLetExecutor.cs @@ -0,0 +1,89 @@ +using SqlDatabase.Adapter; +using SqlDatabase.CommandLine; +using SqlDatabase.Configuration; + +namespace SqlDatabase.PowerShell.Internal; + +public static class CmdLetExecutor +{ + // only for tests + internal static ISqlDatabaseProgram? Program { get; set; } + + public static void RunCreate(IDictionary param, string currentDirectory, Action writeInfo, Action writeError) + { + var commandLine = new CreateCommandLine + { + Database = (string)param[nameof(CreateCommandLine.Database)]!, + Configuration = (string?)param[nameof(CreateCommandLine.Configuration)], + Log = (string?)param[nameof(CreateCommandLine.Log)], + WhatIf = (bool)param[nameof(CreateCommandLine.WhatIf)]! + }; + + CommandLineTools.AppendFrom(commandLine.From, false, (string[]?)param[nameof(CreateCommandLine.From)]); + CommandLineTools.AppendVariables(commandLine.Variables, (string[]?)param[nameof(CreateCommandLine.Variables)]); + + Run(commandLine, currentDirectory, writeInfo, writeError); + } + + public static void RunExecute(IDictionary param, string currentDirectory, Action writeInfo, Action writeError) + { + var commandLine = new ExecuteCommandLine + { + Database = (string)param[nameof(ExecuteCommandLine.Database)]!, + Transaction = (TransactionMode)((int)param[nameof(ExecuteCommandLine.Transaction)]!), + Configuration = (string?)param[nameof(ExecuteCommandLine.Configuration)], + Log = (string?)param[nameof(ExecuteCommandLine.Log)], + WhatIf = (bool)param[nameof(ExecuteCommandLine.WhatIf)]! + }; + + CommandLineTools.AppendFrom(commandLine.From, false, (string[]?)param[nameof(ExecuteCommandLine.From)]); + CommandLineTools.AppendFrom(commandLine.From, true, (string[]?)param["FromSql"]); + CommandLineTools.AppendVariables(commandLine.Variables, (string[]?)param[nameof(ExecuteCommandLine.Variables)]); + + Run(commandLine, currentDirectory, writeInfo, writeError); + } + + public static void RunExport(IDictionary param, string currentDirectory, Action writeInfo, Action writeError) + { + var commandLine = new ExportCommandLine + { + Database = (string)param[nameof(ExportCommandLine.Database)]!, + Configuration = (string?)param[nameof(ExportCommandLine.Configuration)], + DestinationFileName = (string?)param[nameof(ExportCommandLine.DestinationFileName)], + DestinationTableName = (string?)param[nameof(ExportCommandLine.DestinationTableName)], + Log = (string?)param[nameof(ExecuteCommandLine.Log)] + }; + + CommandLineTools.AppendFrom(commandLine.From, false, (string[]?)param[nameof(ExportCommandLine.From)]); + CommandLineTools.AppendFrom(commandLine.From, true, (string[]?)param["FromSql"]); + CommandLineTools.AppendVariables(commandLine.Variables, (string[]?)param[nameof(ExportCommandLine.Variables)]); + + Run(commandLine, currentDirectory, writeInfo, writeError); + } + + public static void RunUpdate(IDictionary param, string currentDirectory, Action writeInfo, Action writeError) + { + var commandLine = new UpgradeCommandLine + { + Database = (string)param[nameof(UpgradeCommandLine.Database)]!, + Transaction = (TransactionMode)((int)param[nameof(UpgradeCommandLine.Transaction)]!), + Configuration = (string?)param[nameof(UpgradeCommandLine.Configuration)], + Log = (string?)param[nameof(UpgradeCommandLine.Log)], + WhatIf = (bool)param[nameof(UpgradeCommandLine.WhatIf)]!, + FolderAsModuleName = (bool)param[nameof(UpgradeCommandLine.FolderAsModuleName)]! + }; + + CommandLineTools.AppendFrom(commandLine.From, false, (string[]?)param[nameof(UpgradeCommandLine.From)]); + CommandLineTools.AppendVariables(commandLine.Variables, (string[]?)param[nameof(UpgradeCommandLine.Variables)]); + + Run(commandLine, currentDirectory, writeInfo, writeError); + } + + public static string GetDefaultConfigurationFile() => ConfigurationManager.GetDefaultConfigurationFile(); + + private static void Run(ICommandLine commandLine, string currentDirectory, Action writeInfo, Action writeError) + { + var program = Program ?? new SqlDatabaseProgram(new CmdLetLogger(writeInfo, writeError), currentDirectory); + program.ExecuteCommand(commandLine); + } +} \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell.Internal/CmdLetLogger.cs b/Sources/SqlDatabase.PowerShell.Internal/CmdLetLogger.cs new file mode 100644 index 00000000..c7a43b48 --- /dev/null +++ b/Sources/SqlDatabase.PowerShell.Internal/CmdLetLogger.cs @@ -0,0 +1,19 @@ +using SqlDatabase.Log; + +namespace SqlDatabase.PowerShell.Internal; + +internal sealed class CmdLetLogger : LoggerBase +{ + private readonly Action _writeInfo; + private readonly Action _writeError; + + public CmdLetLogger(Action writeInfo, Action writeError) + { + _writeInfo = writeInfo; + _writeError = writeError; + } + + protected override void WriteError(string message) => _writeError(message); + + protected override void WriteInfo(string message) => _writeInfo(message); +} \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/CommandLineTools.cs b/Sources/SqlDatabase.PowerShell.Internal/CommandLineTools.cs similarity index 100% rename from Sources/SqlDatabase.PowerShell/Internal/CommandLineTools.cs rename to Sources/SqlDatabase.PowerShell.Internal/CommandLineTools.cs diff --git a/Sources/SqlDatabase.PowerShell/Internal/ISqlDatabaseProgram.cs b/Sources/SqlDatabase.PowerShell.Internal/ISqlDatabaseProgram.cs similarity index 100% rename from Sources/SqlDatabase.PowerShell/Internal/ISqlDatabaseProgram.cs rename to Sources/SqlDatabase.PowerShell.Internal/ISqlDatabaseProgram.cs diff --git a/Sources/SqlDatabase.PowerShell.Internal/Properties/AssemblyInfo.cs b/Sources/SqlDatabase.PowerShell.Internal/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5a6f68ea --- /dev/null +++ b/Sources/SqlDatabase.PowerShell.Internal/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SqlDatabase.PowerShell.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010055AB0DC1F8A24FB41E7358B65A606EC92141F1ABAFBFF062635AB5FAEB22308CFFBC8B54F3436694F14F6FD6C145D4F16C13A3E739FFCA837902BB78E2D51B890D964CC7384C2CC6B844AE37323F501F29E3EDC2DFADA82C99F5FBB5197ED757D795C2E5408DCB3FBAF9DDDF39E60B137ED0A23603A361EA811E6ADB605DFECC")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell.Internal/SqlDatabase.PowerShell.Internal.csproj b/Sources/SqlDatabase.PowerShell.Internal/SqlDatabase.PowerShell.Internal.csproj new file mode 100644 index 00000000..358a3287 --- /dev/null +++ b/Sources/SqlDatabase.PowerShell.Internal/SqlDatabase.PowerShell.Internal.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + ..\..\bin\SqlDatabase.PowerShell + false + + + + + + + diff --git a/Sources/SqlDatabase.PowerShell/Internal/SqlDatabaseProgram.cs b/Sources/SqlDatabase.PowerShell.Internal/SqlDatabaseProgram.cs similarity index 75% rename from Sources/SqlDatabase.PowerShell/Internal/SqlDatabaseProgram.cs rename to Sources/SqlDatabase.PowerShell.Internal/SqlDatabaseProgram.cs index 753c5d69..fb808dc9 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/SqlDatabaseProgram.cs +++ b/Sources/SqlDatabase.PowerShell.Internal/SqlDatabaseProgram.cs @@ -14,8 +14,5 @@ public SqlDatabaseProgram(ILogger logger, string currentDirectory) _currentDirectory = currentDirectory; } - public void ExecuteCommand(ICommandLine command) - { - Program.RunPowershell(_logger, command, _currentDirectory); - } + public void ExecuteCommand(ICommandLine command) => Program.RunPowershell(_logger, command, _currentDirectory); } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell.Test/Internal/DependencyResolverFactoryTest.cs b/Sources/SqlDatabase.PowerShell.Test/Internal/DependencyResolverFactoryTest.cs index 894df28e..2429fe20 100644 --- a/Sources/SqlDatabase.PowerShell.Test/Internal/DependencyResolverFactoryTest.cs +++ b/Sources/SqlDatabase.PowerShell.Test/Internal/DependencyResolverFactoryTest.cs @@ -8,10 +8,9 @@ namespace SqlDatabase.PowerShell.Internal; public class DependencyResolverFactoryTest { [Test] - [TestCase("Desktop", typeof(PowerShellDesktopDependencyResolver))] - [TestCase(null, typeof(PowerShellDesktopDependencyResolver))] - [TestCase("Core", typeof(PowerShellCoreDependencyResolver))] - public void CreateProgram(string? psEdition, Type expected) + [TestCase("Desktop")] + [TestCase(null)] + public void CreateDesktop(string? psEdition) { var psVersionTable = new Hashtable(); if (psEdition != null) @@ -21,6 +20,19 @@ public void CreateProgram(string? psEdition, Type expected) var actual = DependencyResolverFactory.Create(new PSVersionTable(psVersionTable)); - actual.ShouldBeOfType(expected); + actual.ShouldBeOfType(); + } + + [Test] + public void CreateCore() + { + var psVersionTable = new Hashtable + { + { "PSEdition", "Core" } + }; + + var ex = Should.Throw(() => DependencyResolverFactory.Create(new PSVersionTable(psVersionTable))); + + ex.Message.ShouldContain("System.Runtime.Loader"); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell.Test/TestApi/SqlDatabaseCmdLetTest.cs b/Sources/SqlDatabase.PowerShell.Test/TestApi/SqlDatabaseCmdLetTest.cs index 7624da95..6877dd81 100644 --- a/Sources/SqlDatabase.PowerShell.Test/TestApi/SqlDatabaseCmdLetTest.cs +++ b/Sources/SqlDatabase.PowerShell.Test/TestApi/SqlDatabaseCmdLetTest.cs @@ -40,13 +40,13 @@ public void BeforeEachTest() .Callback(_commandLines.Add); _commandLines.Clear(); - PowerShellCommand.Program = program.Object; + CmdLetExecutor.Program = program.Object; } [TearDown] public void AfterEachTest() { - PowerShellCommand.Program = null; + CmdLetExecutor.Program = null; foreach (var row in _powerShell.Streams.Information) { diff --git a/Sources/SqlDatabase.PowerShell/CreateCmdLet.cs b/Sources/SqlDatabase.PowerShell/CreateCmdLet.cs index dd3ddf53..698b1d02 100644 --- a/Sources/SqlDatabase.PowerShell/CreateCmdLet.cs +++ b/Sources/SqlDatabase.PowerShell/CreateCmdLet.cs @@ -32,17 +32,15 @@ public sealed class CreateCmdLet : PSCmdlet protected override void ProcessRecord() { - var commandLine = new CreateCommandLine + var param = new Dictionary { - Database = Database, - Configuration = Configuration, - Log = Log, - WhatIf = WhatIf + { nameof(CreateCommandLine.Database), Database }, + { nameof(CreateCommandLine.Configuration), Configuration }, + { nameof(CreateCommandLine.Log), Log }, + { nameof(CreateCommandLine.WhatIf), (bool)WhatIf }, + { nameof(CreateCommandLine.From), From }, + { nameof(CreateCommandLine.Variables), Var } }; - - CommandLineTools.AppendFrom(commandLine.From, false, From); - CommandLineTools.AppendVariables(commandLine.Variables, Var); - - PowerShellCommand.Execute(this, commandLine); + PowerShellCommand.Execute(this, nameof(CmdLetExecutor.RunCreate), param); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/ExecuteCmdLet.cs b/Sources/SqlDatabase.PowerShell/ExecuteCmdLet.cs index 31c81f8a..617c30bd 100644 --- a/Sources/SqlDatabase.PowerShell/ExecuteCmdLet.cs +++ b/Sources/SqlDatabase.PowerShell/ExecuteCmdLet.cs @@ -1,5 +1,4 @@ using System.Management.Automation; -using SqlDatabase.Adapter; using SqlDatabase.CommandLine; using SqlDatabase.PowerShell.Internal; @@ -41,19 +40,17 @@ public sealed class ExecuteCmdLet : PSCmdlet protected override void ProcessRecord() { - var commandLine = new ExecuteCommandLine + var param = new Dictionary { - Database = Database, - Transaction = (TransactionMode)Transaction, - Configuration = Configuration, - Log = Log, - WhatIf = WhatIf + { nameof(ExecuteCommandLine.Database), Database }, + { nameof(ExecuteCommandLine.Transaction), (int)Transaction }, + { nameof(ExecuteCommandLine.Configuration), Configuration }, + { nameof(ExecuteCommandLine.Log), Log }, + { nameof(ExecuteCommandLine.WhatIf), (bool)WhatIf }, + { nameof(ExecuteCommandLine.From), From }, + { "FromSql", FromSql }, + { nameof(ExecuteCommandLine.Variables), Var } }; - - CommandLineTools.AppendFrom(commandLine.From, false, From); - CommandLineTools.AppendFrom(commandLine.From, true, FromSql); - CommandLineTools.AppendVariables(commandLine.Variables, Var); - - PowerShellCommand.Execute(this, commandLine); + PowerShellCommand.Execute(this, nameof(CmdLetExecutor.RunExecute), param); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/ExportCmdLet.cs b/Sources/SqlDatabase.PowerShell/ExportCmdLet.cs index 78b288c4..64966f87 100644 --- a/Sources/SqlDatabase.PowerShell/ExportCmdLet.cs +++ b/Sources/SqlDatabase.PowerShell/ExportCmdLet.cs @@ -38,19 +38,17 @@ public sealed class ExportCmdLet : PSCmdlet protected override void ProcessRecord() { - var commandLine = new ExportCommandLine + var param = new Dictionary { - Database = Database, - Configuration = Configuration, - DestinationFileName = ToFile, - DestinationTableName = ToTable, - Log = Log + { nameof(ExportCommandLine.Database), Database }, + { nameof(ExportCommandLine.Configuration), Configuration }, + { nameof(ExportCommandLine.DestinationFileName), ToFile }, + { nameof(ExportCommandLine.DestinationTableName), ToTable }, + { nameof(ExportCommandLine.Log), Log }, + { nameof(ExportCommandLine.From), From }, + { "FromSql", FromSql }, + { nameof(ExportCommandLine.Variables), Var } }; - - CommandLineTools.AppendFrom(commandLine.From, false, From); - CommandLineTools.AppendFrom(commandLine.From, true, FromSql); - CommandLineTools.AppendVariables(commandLine.Variables, Var); - - PowerShellCommand.Execute(this, commandLine); + PowerShellCommand.Execute(this, nameof(CmdLetExecutor.RunExport), param); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/InfoCmdLet.cs b/Sources/SqlDatabase.PowerShell/InfoCmdLet.cs index 7c675884..2ae770c4 100644 --- a/Sources/SqlDatabase.PowerShell/InfoCmdLet.cs +++ b/Sources/SqlDatabase.PowerShell/InfoCmdLet.cs @@ -1,6 +1,5 @@ using System.Management.Automation; using System.Runtime.InteropServices; -using SqlDatabase.Configuration; using SqlDatabase.PowerShell.Internal; namespace SqlDatabase.PowerShell; @@ -9,15 +8,6 @@ namespace SqlDatabase.PowerShell; public sealed class InfoCmdLet : PSCmdlet { protected override void ProcessRecord() - { - using (var resolver = DependencyResolverFactory.Create(this)) - { - resolver.Initialize(); - WriteInfo(); - } - } - - private void WriteInfo() { var assembly = GetType().Assembly; @@ -33,9 +23,9 @@ private void WriteInfo() RuntimeInformation.OSDescription, RuntimeInformation.OSArchitecture, RuntimeInformation.ProcessArchitecture, - Location = Path.GetDirectoryName(assembly.Location), + Location = assembly.GetDirectoryLocation(), WorkingDirectory = this.GetWorkingDirectory(), - DefaultConfigurationFile = ConfigurationManager.GetDefaultConfigurationFile() + DefaultConfigurationFile = PowerShellCommand.GetDefaultConfigurationFile(this) }); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/AssemblyCache.cs b/Sources/SqlDatabase.PowerShell/Internal/AssemblyCache.cs index da3b8bab..32f21d92 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/AssemblyCache.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/AssemblyCache.cs @@ -2,39 +2,19 @@ namespace SqlDatabase.PowerShell.Internal; -internal sealed class AssemblyCache : IDisposable +internal sealed class AssemblyCache { private readonly string[] _probingPaths; - private readonly IDictionary _assemblyByName; public AssemblyCache(params string[] probingPaths) { - _probingPaths = new string[probingPaths.Length + 1]; - _probingPaths[0] = Path.GetDirectoryName(GetType().Assembly.Location); - for (var i = 0; i < probingPaths.Length; i++) - { - _probingPaths[i + 1] = probingPaths[i]; - } - - _assemblyByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _probingPaths = probingPaths; } public Assembly? Load(AssemblyName assemblyName, Func loader) { var fileName = assemblyName.Name + ".dll"; - if (_assemblyByName.TryGetValue(fileName, out var assembly)) - { - return assembly; - } - - assembly = TryFindAndLoad(fileName, loader); - _assemblyByName[fileName] = assembly; - return assembly; - } - - public void Dispose() - { - _assemblyByName.Clear(); + return TryFindAndLoad(fileName, loader); } private Assembly? TryFindAndLoad(string fileName, Func loader) diff --git a/Sources/SqlDatabase.PowerShell/Internal/CmdLetExecutorInvoker.cs b/Sources/SqlDatabase.PowerShell/Internal/CmdLetExecutorInvoker.cs new file mode 100644 index 00000000..83b0af42 --- /dev/null +++ b/Sources/SqlDatabase.PowerShell/Internal/CmdLetExecutorInvoker.cs @@ -0,0 +1,73 @@ +using System.Reflection; + +namespace SqlDatabase.PowerShell.Internal; + +internal sealed class CmdLetExecutorInvoker +{ + private const string ExecutorAssembly = "SqlDatabase.PowerShell.Internal"; + private const string ExecutorType = $"{ExecutorAssembly}.CmdLetExecutor"; + + private readonly IDependencyResolver _dependencyResolver; + private readonly Type _executorType; + + public CmdLetExecutorInvoker(IDependencyResolver dependencyResolver) + { + _dependencyResolver = dependencyResolver; + _executorType = ResolveExecutor(dependencyResolver); + } + + private delegate void Run(IDictionary param, string currentDirectory, Action writeInfo, Action writeError); + + public void Invoke(CmdLetLogger logger, string workingDirectory, string methodName, IDictionary param) + { + _dependencyResolver.Attach(); + try + { + var method = (Run)ResolveMethod(methodName).CreateDelegate(typeof(Run)); + method(param, workingDirectory, logger.Info, logger.Error); + } + finally + { + _dependencyResolver.Detach(); + } + } + + public string GetDefaultConfigurationFile() + { + _dependencyResolver.Attach(); + try + { + var method = (Func)ResolveMethod(nameof(CmdLetExecutor.GetDefaultConfigurationFile)).CreateDelegate(typeof(Func)); + return method(); + } + finally + { + _dependencyResolver.Detach(); + } + } + + private static Type ResolveExecutor(IDependencyResolver dependencyResolver) + { + dependencyResolver.Attach(); + try + { + var assembly = dependencyResolver.LoadDependency(ExecutorAssembly); + if (assembly == null) + { + throw new InvalidOperationException($"Dependency {ExecutorAssembly}.dll not found"); + } + + return assembly.GetType(ExecutorType, throwOnError: true, ignoreCase: false); + } + finally + { + dependencyResolver.Detach(); + } + } + + private MethodInfo ResolveMethod(string name) + { + var method = _executorType.GetMethod(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); + return method ?? throw new InvalidOperationException($"Method {_executorType.FullName}.{name} not found"); + } +} \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/CmdLetLogger.cs b/Sources/SqlDatabase.PowerShell/Internal/CmdLetLogger.cs index c9980719..51ae9372 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/CmdLetLogger.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/CmdLetLogger.cs @@ -1,28 +1,27 @@ using System.Management.Automation; -using SqlDatabase.Log; namespace SqlDatabase.PowerShell.Internal; -internal sealed class CmdLetLogger : LoggerBase +internal sealed class CmdLetLogger { private readonly Cmdlet _cmdlet; public CmdLetLogger(Cmdlet cmdlet) { _cmdlet = cmdlet; + Info = WriteInfo; + Error = WriteError; } - protected override void WriteError(string message) - { - _cmdlet.WriteError(new ErrorRecord( - new InvalidOperationException(message), - null, - ErrorCategory.NotSpecified, - null)); - } + public Action Info { get; } - protected override void WriteInfo(string message) - { - _cmdlet.WriteInformation(new InformationRecord(message, null)); - } + public Action Error { get; } + + private void WriteError(string message) => _cmdlet.WriteError(new ErrorRecord( + new InvalidOperationException(message), + null, + ErrorCategory.NotSpecified, + null)); + + private void WriteInfo(string message) => _cmdlet.WriteInformation(new InformationRecord(message, null)); } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/CmdletExtensions.cs b/Sources/SqlDatabase.PowerShell/Internal/CmdletExtensions.cs index 311472b5..9ce04b79 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/CmdletExtensions.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/CmdletExtensions.cs @@ -1,4 +1,5 @@ using System.Management.Automation; +using System.Reflection; namespace SqlDatabase.PowerShell.Internal; @@ -27,4 +28,15 @@ public static bool TryGetPSVersionTable(this PSCmdlet cmdlet, out PSVersionTable value = new PSVersionTable(psVersionTable); return true; } + + public static string GetDirectoryLocation(this Assembly assembly) + { + var location = assembly.Location; + if (string.IsNullOrEmpty(location) || !File.Exists(location)) + { + throw new InvalidOperationException($"Location of {assembly.FullName} not found '{location}'."); + } + + return Path.GetDirectoryName(location)!; + } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/IDependencyResolver.cs b/Sources/SqlDatabase.PowerShell/Internal/IDependencyResolver.cs index e7b9cd82..20d79759 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/IDependencyResolver.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/IDependencyResolver.cs @@ -1,6 +1,12 @@ -namespace SqlDatabase.PowerShell.Internal; +using System.Reflection; -internal interface IDependencyResolver : IDisposable +namespace SqlDatabase.PowerShell.Internal; + +internal interface IDependencyResolver { - void Initialize(); + void Attach(); + + void Detach(); + + Assembly? LoadDependency(string assemblyName); } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/PowerShellCommand.cs b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCommand.cs index 6bd90a91..48bcf7ba 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/PowerShellCommand.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCommand.cs @@ -1,30 +1,34 @@ using System.Management.Automation; -using SqlDatabase.CommandLine; namespace SqlDatabase.PowerShell.Internal; internal static class PowerShellCommand { - // only for tests - internal static ISqlDatabaseProgram? Program { get; set; } + private static CmdLetExecutorInvoker? _invoker; - public static void Execute(PSCmdlet cmdlet, ICommandLine command) + public static void Execute(PSCmdlet cmdlet, string methodName, IDictionary param) { - using (var resolver = DependencyResolverFactory.Create(cmdlet)) + var logger = new CmdLetLogger(cmdlet); + try { - resolver.Initialize(); - - ResolveProgram(cmdlet).ExecuteCommand(command); + GetInvoker(cmdlet).Invoke(logger, cmdlet.GetWorkingDirectory(), methodName, param); + } + catch (Exception ex) + { + logger.Error(ex.Message); + throw; } } - private static ISqlDatabaseProgram ResolveProgram(PSCmdlet cmdlet) + public static string GetDefaultConfigurationFile(PSCmdlet cmdlet) => GetInvoker(cmdlet).GetDefaultConfigurationFile(); + + private static CmdLetExecutorInvoker GetInvoker(PSCmdlet cmdlet) { - if (Program != null) + if (_invoker == null) { - return Program; + _invoker = new CmdLetExecutorInvoker(DependencyResolverFactory.Create(cmdlet)); } - return new SqlDatabaseProgram(new CmdLetLogger(cmdlet), cmdlet.GetWorkingDirectory()); + return _invoker; } } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreAssemblyContext.cs b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreAssemblyContext.cs new file mode 100644 index 00000000..85791d7f --- /dev/null +++ b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreAssemblyContext.cs @@ -0,0 +1,32 @@ +using System.Management.Automation; +using System.Reflection; +using System.Runtime.Loader; + +namespace SqlDatabase.PowerShell.Internal; + +internal sealed class PowerShellCoreAssemblyContext : AssemblyLoadContext +{ + private readonly AssemblyCache _privateCache; + private readonly AssemblyCache _sharedCache; + + public PowerShellCoreAssemblyContext() + { + var common = GetType().Assembly.GetDirectoryLocation(); + + _privateCache = new AssemblyCache(common, Path.Combine(common, "ps-core")); + _sharedCache = new AssemblyCache(typeof(PSCmdlet).Assembly.GetDirectoryLocation()); + } + + public Assembly? LoadDependency(string assemblyName) => Load(new AssemblyName(assemblyName)); + + protected override Assembly? Load(AssemblyName assemblyName) + { + var result = _privateCache.Load(assemblyName, LoadFromAssemblyPath); + if (result == null) + { + result = _sharedCache.Load(assemblyName, Default.LoadFromAssemblyPath); + } + + return result; + } +} \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreDependencyResolver.cs b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreDependencyResolver.cs index 243545e6..7e132ac9 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreDependencyResolver.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/PowerShellCoreDependencyResolver.cs @@ -1,36 +1,18 @@ -using System.Management.Automation; -using System.Reflection; -using System.Runtime.Loader; +using System.Reflection; namespace SqlDatabase.PowerShell.Internal; internal sealed class PowerShellCoreDependencyResolver : IDependencyResolver { - private readonly AssemblyCache _cache; + private readonly PowerShellCoreAssemblyContext _context = new(); - public PowerShellCoreDependencyResolver() + public void Attach() { - var psCore = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), "ps-core"); - _cache = new AssemblyCache( - psCore, - Path.GetDirectoryName(typeof(PSCmdlet).Assembly.Location)); } - public void Initialize() + public void Detach() { - // Fail to load configuration from [SqlDatabase.exe.config]. - // ---> An error occurred creating the configuration section handler for sqlDatabase: Could not load file or assembly 'SqlDatabase, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified. - AssemblyLoadContext.Default.Resolving += AssemblyResolving; } - public void Dispose() - { - AssemblyLoadContext.Default.Resolving -= AssemblyResolving; - _cache.Dispose(); - } - - private Assembly? AssemblyResolving(AssemblyLoadContext context, AssemblyName assemblyName) - { - return _cache.Load(assemblyName, context.LoadFromAssemblyPath); - } + public Assembly? LoadDependency(string assemblyName) => _context.LoadDependency(assemblyName); } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/Internal/PowerShellDesktopDependencyResolver.cs b/Sources/SqlDatabase.PowerShell/Internal/PowerShellDesktopDependencyResolver.cs index 7909698f..5af5c66b 100644 --- a/Sources/SqlDatabase.PowerShell/Internal/PowerShellDesktopDependencyResolver.cs +++ b/Sources/SqlDatabase.PowerShell/Internal/PowerShellDesktopDependencyResolver.cs @@ -5,26 +5,31 @@ namespace SqlDatabase.PowerShell.Internal; internal sealed class PowerShellDesktopDependencyResolver : IDependencyResolver { private readonly AssemblyCache _cache; + private int _refCounter; public PowerShellDesktopDependencyResolver() { - var psDesktop = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), "ps-desktop"); - _cache = new AssemblyCache(psDesktop); + var common = GetType().Assembly.GetDirectoryLocation(); + _cache = new AssemblyCache(common, Path.Combine(common, "ps-desktop")); } - public void Initialize() + public void Attach() { - AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + if (Interlocked.Increment(ref _refCounter) == 1) + { + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + } } - public void Dispose() + public void Detach() { - AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolve; - _cache.Dispose(); + if (Interlocked.Decrement(ref _refCounter) == 0) + { + AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolve; + } } - private Assembly? AssemblyResolve(object sender, ResolveEventArgs args) - { - return _cache.Load(new AssemblyName(args.Name), Assembly.LoadFrom); - } + public Assembly? LoadDependency(string assemblyName) => _cache.Load(new AssemblyName(assemblyName), Assembly.LoadFrom); + + private Assembly? AssemblyResolve(object sender, ResolveEventArgs args) => LoadDependency(args.Name); } \ No newline at end of file diff --git a/Sources/SqlDatabase.PowerShell/SqlDatabase.PowerShell.csproj b/Sources/SqlDatabase.PowerShell/SqlDatabase.PowerShell.csproj index d37044c8..a960add4 100644 --- a/Sources/SqlDatabase.PowerShell/SqlDatabase.PowerShell.csproj +++ b/Sources/SqlDatabase.PowerShell/SqlDatabase.PowerShell.csproj @@ -14,6 +14,7 @@ + diff --git a/Sources/SqlDatabase.PowerShell/UpgradeCmdLet.cs b/Sources/SqlDatabase.PowerShell/UpgradeCmdLet.cs index 8a584af0..44e10e89 100644 --- a/Sources/SqlDatabase.PowerShell/UpgradeCmdLet.cs +++ b/Sources/SqlDatabase.PowerShell/UpgradeCmdLet.cs @@ -1,5 +1,4 @@ using System.Management.Automation; -using SqlDatabase.Adapter; using SqlDatabase.CommandLine; using SqlDatabase.PowerShell.Internal; @@ -40,19 +39,17 @@ public sealed class UpgradeCmdLet : PSCmdlet protected override void ProcessRecord() { - var commandLine = new UpgradeCommandLine + var param = new Dictionary { - Database = Database, - Transaction = (TransactionMode)Transaction, - Configuration = Configuration, - Log = Log, - WhatIf = WhatIf, - FolderAsModuleName = FolderAsModuleName + { nameof(UpgradeCommandLine.Database), Database }, + { nameof(UpgradeCommandLine.Transaction), (int)Transaction }, + { nameof(UpgradeCommandLine.Configuration), Configuration }, + { nameof(UpgradeCommandLine.Log), Log }, + { nameof(UpgradeCommandLine.WhatIf), (bool)WhatIf }, + { nameof(UpgradeCommandLine.FolderAsModuleName), (bool)FolderAsModuleName }, + { nameof(UpgradeCommandLine.From), From }, + { nameof(UpgradeCommandLine.Variables), Var } }; - - CommandLineTools.AppendFrom(commandLine.From, false, From); - CommandLineTools.AppendVariables(commandLine.Variables, Var); - - PowerShellCommand.Execute(this, commandLine); + PowerShellCommand.Execute(this, nameof(CmdLetExecutor.RunUpdate), param); } } \ No newline at end of file diff --git a/Sources/SqlDatabase.sln b/Sources/SqlDatabase.sln index 1d02e208..1b5abbea 100644 --- a/Sources/SqlDatabase.sln +++ b/Sources/SqlDatabase.sln @@ -68,6 +68,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlDatabase.CommandLine", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlDatabase.CommandLine.Test", "SqlDatabase.CommandLine.Test\SqlDatabase.CommandLine.Test.csproj", "{E0ACD46D-CE78-4B04-B0F7-110ACE118EAD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlDatabase.PowerShell.Internal", "SqlDatabase.PowerShell.Internal\SqlDatabase.PowerShell.Internal.csproj", "{87EE2F0A-DF01-4035-B202-BE5F96C09FFA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -186,6 +188,10 @@ Global {E0ACD46D-CE78-4B04-B0F7-110ACE118EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0ACD46D-CE78-4B04-B0F7-110ACE118EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0ACD46D-CE78-4B04-B0F7-110ACE118EAD}.Release|Any CPU.Build.0 = Release|Any CPU + {87EE2F0A-DF01-4035-B202-BE5F96C09FFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87EE2F0A-DF01-4035-B202-BE5F96C09FFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87EE2F0A-DF01-4035-B202-BE5F96C09FFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87EE2F0A-DF01-4035-B202-BE5F96C09FFA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Sources/SqlDatabase/Properties/AssemblyInfo.cs b/Sources/SqlDatabase/Properties/AssemblyInfo.cs index d0e8c551..d5ba70a6 100644 --- a/Sources/SqlDatabase/Properties/AssemblyInfo.cs +++ b/Sources/SqlDatabase/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("SqlDatabase.PowerShell, PublicKey=002400000480000094000000060200000024000052534131000400000100010055AB0DC1F8A24FB41E7358B65A606EC92141F1ABAFBFF062635AB5FAEB22308CFFBC8B54F3436694F14F6FD6C145D4F16C13A3E739FFCA837902BB78E2D51B890D964CC7384C2CC6B844AE37323F501F29E3EDC2DFADA82C99F5FBB5197ED757D795C2E5408DCB3FBAF9DDDF39E60B137ED0A23603A361EA811E6ADB605DFECC")] +[assembly: InternalsVisibleTo("SqlDatabase.PowerShell.Internal, PublicKey=002400000480000094000000060200000024000052534131000400000100010055AB0DC1F8A24FB41E7358B65A606EC92141F1ABAFBFF062635AB5FAEB22308CFFBC8B54F3436694F14F6FD6C145D4F16C13A3E739FFCA837902BB78E2D51B890D964CC7384C2CC6B844AE37323F501F29E3EDC2DFADA82C99F5FBB5197ED757D795C2E5408DCB3FBAF9DDDF39E60B137ED0A23603A361EA811E6ADB605DFECC")] [assembly: InternalsVisibleTo("SqlDatabase.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010055AB0DC1F8A24FB41E7358B65A606EC92141F1ABAFBFF062635AB5FAEB22308CFFBC8B54F3436694F14F6FD6C145D4F16C13A3E739FFCA837902BB78E2D51B890D964CC7384C2CC6B844AE37323F501F29E3EDC2DFADA82C99F5FBB5197ED757D795C2E5408DCB3FBAF9DDDF39E60B137ED0A23603A361EA811E6ADB605DFECC")] [assembly: InternalsVisibleTo("SqlDatabase.PowerShell.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010055AB0DC1F8A24FB41E7358B65A606EC92141F1ABAFBFF062635AB5FAEB22308CFFBC8B54F3436694F14F6FD6C145D4F16C13A3E739FFCA837902BB78E2D51B890D964CC7384C2CC6B844AE37323F501F29E3EDC2DFADA82C99F5FBB5197ED757D795C2E5408DCB3FBAF9DDDF39E60B137ED0A23603A361EA811E6ADB605DFECC")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file