From a4005c9a9c872f472b08092661d68f54e35225eb Mon Sep 17 00:00:00 2001 From: John Bruckler Date: Sun, 22 Oct 2023 15:30:56 -0400 Subject: [PATCH] everything is broken currently, but moving computers :) --- ...GetPSSconfigItem.cs => GetPSSconfigItem.cs | 0 ImportPSSconfig.cs | 22 +++ Output/PSSimpleConfig/PSSimpleConfig.json | 12 ++ .../RemovePSSConfig.cs => RemovePSSConfig.cs | 0 ...PSSConfigItem.cs => RemovePSSConfigItem.cs | 0 ...SetPSSConfigItem.cs => SetPSSConfigItem.cs | 0 src/Cmdlets/ImportPSSconfig.cs | 13 +- src/Cmdlets/NewPSSConfig.cs | 126 ++++------------- src/Project.cs | 28 ++++ src/Utilities/PSSimpleConfig.cs | 130 ++++++++++-------- src/Utilities/PSSimpleConfigSingleton.cs | 48 +++++++ 11 files changed, 210 insertions(+), 169 deletions(-) rename src/Cmdlets/GetPSSconfigItem.cs => GetPSSconfigItem.cs (100%) create mode 100644 ImportPSSconfig.cs create mode 100644 Output/PSSimpleConfig/PSSimpleConfig.json rename src/Cmdlets/RemovePSSConfig.cs => RemovePSSConfig.cs (100%) rename src/Cmdlets/RemovePSSConfigItem.cs => RemovePSSConfigItem.cs (100%) rename src/Cmdlets/SetPSSConfigItem.cs => SetPSSConfigItem.cs (100%) create mode 100644 src/Project.cs create mode 100644 src/Utilities/PSSimpleConfigSingleton.cs diff --git a/src/Cmdlets/GetPSSconfigItem.cs b/GetPSSconfigItem.cs similarity index 100% rename from src/Cmdlets/GetPSSconfigItem.cs rename to GetPSSconfigItem.cs diff --git a/ImportPSSconfig.cs b/ImportPSSconfig.cs new file mode 100644 index 0000000..0ce2d4c --- /dev/null +++ b/ImportPSSconfig.cs @@ -0,0 +1,22 @@ +using System.Management.Automation; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PSSimpleConfig.Utilities; + +namespace PSSimpleConfig.Cmdlets; + +[Cmdlet(VerbsData.Import, "PSSConfig")] +public class ImportPSSConfig : PSCmdlet +{ + [Parameter(Mandatory = true)] + [Alias("Project")] + public string Name { get; set; } + + protected override void ProcessRecord() + { + JObject config = PSSimpleConfig.ImportConfig(Name); + SessionState.PSVariable.Set("PSSimpleConfig", JsonConversion.ToPSOutput(config)); + WriteObject(SessionState.PSVariable.Get("PSSimpleConfig")); + } +} \ No newline at end of file diff --git a/Output/PSSimpleConfig/PSSimpleConfig.json b/Output/PSSimpleConfig/PSSimpleConfig.json new file mode 100644 index 0000000..7faf318 --- /dev/null +++ b/Output/PSSimpleConfig/PSSimpleConfig.json @@ -0,0 +1,12 @@ +{ + "ModuleCfgRoot": "C:\\Users\\u55398\\Documents\\git\\PSSimpleConfig\\Output\\PSSimpleConfig", + "ModuleCfgFile": "C:\\Users\\u55398\\Documents\\git\\PSSimpleConfig\\Output\\PSSimpleConfig\\PSSimpleConfig.json", + "ProjectCfgRoot": "C:\\ProgramData\\PSSimpleConfig\\Projects", + "Projects": [ + { + "id": "6a2c1dd2-c06f-4427-a072-2e8fe230ff0a", + "name": "TestProject", + "root": "C:\\Users\\u55398\\Documents\\git\\PSSimpleConfig\\Output\\PSSimpleConfig\\TestProject" + } + ] +} \ No newline at end of file diff --git a/src/Cmdlets/RemovePSSConfig.cs b/RemovePSSConfig.cs similarity index 100% rename from src/Cmdlets/RemovePSSConfig.cs rename to RemovePSSConfig.cs diff --git a/src/Cmdlets/RemovePSSConfigItem.cs b/RemovePSSConfigItem.cs similarity index 100% rename from src/Cmdlets/RemovePSSConfigItem.cs rename to RemovePSSConfigItem.cs diff --git a/src/Cmdlets/SetPSSConfigItem.cs b/SetPSSConfigItem.cs similarity index 100% rename from src/Cmdlets/SetPSSConfigItem.cs rename to SetPSSConfigItem.cs diff --git a/src/Cmdlets/ImportPSSconfig.cs b/src/Cmdlets/ImportPSSconfig.cs index 0ce2d4c..5900603 100644 --- a/src/Cmdlets/ImportPSSconfig.cs +++ b/src/Cmdlets/ImportPSSconfig.cs @@ -6,17 +6,8 @@ namespace PSSimpleConfig.Cmdlets; -[Cmdlet(VerbsData.Import, "PSSConfig")] -public class ImportPSSConfig : PSCmdlet +[Cmdlet(VerbsData.Import, "PSSConfig", SupportsShouldProcess = false, ConfirmImpact = ConfirmImpact.None)] +public class ImportPSSConfig { - [Parameter(Mandatory = true)] - [Alias("Project")] - public string Name { get; set; } - protected override void ProcessRecord() - { - JObject config = PSSimpleConfig.ImportConfig(Name); - SessionState.PSVariable.Set("PSSimpleConfig", JsonConversion.ToPSOutput(config)); - WriteObject(SessionState.PSVariable.Get("PSSimpleConfig")); - } } \ No newline at end of file diff --git a/src/Cmdlets/NewPSSConfig.cs b/src/Cmdlets/NewPSSConfig.cs index d183626..be4cb1a 100644 --- a/src/Cmdlets/NewPSSConfig.cs +++ b/src/Cmdlets/NewPSSConfig.cs @@ -1,18 +1,15 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Collections.ObjectModel; +using System.Management.Automation; using Newtonsoft.Json; -using System.ServiceModel.Security.Tokens; +using Newtonsoft.Json.Linq; +using PSSimpleConfig.Utilities; namespace PSSimpleConfig.Cmdlets; [Cmdlet(VerbsCommon.New, "PSSConfig", SupportsShouldProcess = false, ConfirmImpact = ConfirmImpact.None)] public class NewPSSConfig : PSCmdlet { - [Parameter(Mandatory = false, Position = 1)] + [Parameter(Mandatory = true, Position = 1)] + [ValidateNotNullOrEmpty()] [Alias("Project")] public string? Name { get; set; } @@ -23,110 +20,45 @@ public class NewPSSConfig : PSCmdlet [Parameter(Mandatory = false)] public SwitchParameter Force { get; set; } + [Parameter(Mandatory = false)] + public System.IO.DirectoryInfo? Path { get; set; } + [Parameter(Mandatory = false)] public SwitchParameter FromPSModule { get; set; } protected override void BeginProcessing() { - + PSSC.InitializeModule(this); } protected override void ProcessRecord() { - PSSimpleConfig.SetScope(Scope); - WriteDebug($"PSSimpleConfig.Scope: {PSSimpleConfig.Scope}"); - WriteDebug($"PSSimpleConfig.Root: {PSSimpleConfig.Root}"); - WriteDebug($"PSSimpleConfig.ProjectRoot: {PSSimpleConfig.ProjectRoot}"); + WriteDebug($"PSSCCfgRoot: {PSSC.Instance.ModuleData["PSSCCfgRoot"]}"); + WriteDebug($"PSSCCfgFile: {PSSC.Instance.ModuleData["PSSCCfgFile"]}"); + WriteDebug($"ProjectRoot: {PSSC.Instance.ModuleData["ProjectRoot"]}"); + + // Need to check if there's already a project with the same name. - // Create a PowerShell instance for retrieving module information - using PowerShell ps = PowerShell.Create(); + Guid projectGuid = Guid.NewGuid(); - try + bool v = Path == null; + #pragma warning disable CS8618 // Nullability warning. We're checking nulls above. + string projectPath = v ? System.IO.Path.Combine(PSSC.Instance.ModuleData["ProjectRoot"].ToString(), Name) : Path.FullName; + #pragma warning restore CS8618 // Nullability warning. + + // Check if the project directory exists. If it does, check if we're forcing overwrite. + if (System.IO.Directory.Exists(projectPath)) { - // Create the root (PSSimpleConfig) directory if it doesn't exist - if (!Directory.Exists(PSSimpleConfig.Root)) - { - WriteDebug($"PSSimpleConfig root directory not found. Creating directory: {PSSimpleConfig.Root}"); - WriteVerbose($"Creating PSSimpleConfig Root directory: {PSSimpleConfig.Root}"); - Directory.CreateDirectory(PSSimpleConfig.Root); - } - // Create the projects directory if it doesn't exist - // root/projects - if (!Directory.Exists(PSSimpleConfig.ProjectRoot)) + if (Force) { - WriteDebug($"PSSimpleConfig projects directory not found. Creating directory: {PSSimpleConfig.ProjectRoot}"); - WriteVerbose($"Creating project directory: {PSSimpleConfig.ProjectRoot}"); - Directory.CreateDirectory(PSSimpleConfig.ProjectRoot); + WriteWarning($"Project directory {projectPath} already exists. Forcing overwrite."); + System.IO.Directory.Delete(projectPath, true); + System.IO.Directory.CreateDirectory(projectPath); } - // Create the project directory if it doesn't exist - // root/projects/{Name} - if (!string.IsNullOrEmpty(Name)) + else { - string projectFolder = Path.Combine(PSSimpleConfig.ProjectRoot, Name); - bool _createProject = false; - if (!Directory.Exists(projectFolder)) - { - _createProject = true; - } - else - { - if (Force == false) - { - WriteWarning($"Project {Name} already exists. Use -Force to overwrite."); - } - else - { - WriteDebug($"Project {Name} exists and -Force is set. Deleting existing project directory: {projectFolder}"); - WriteVerbose($"Deleting existing project directory: {projectFolder}"); - Directory.Delete(projectFolder, true); - _createProject = true; - } - } - if (_createProject) - { - WriteVerbose($"Creating project directory: {projectFolder}"); - Directory.CreateDirectory(projectFolder); - - PSModuleInfo _PSSCMod = this.MyInvocation.MyCommand.Module; - - // Create a Dictionary to hold the initial config.json values. - Dictionary _configDict = new Dictionary(); - Dictionary _psscInfo = new Dictionary - { - // Add the PSSimpleConfig module information to the _psscInfo Dictionary - { "Version", _PSSCMod.Version.ToString() }, - { "ProjectName", Name }, - { "ConfigScope", Scope } - }; - _configDict.Add("PSSC", _psscInfo); - - // Use Get-Module to retrive some basic information about - // the module and add it to the configObject. - if (FromPSModule) - { - ps.AddCommand("Get-Module").AddParameter("Name", Name).AddParameter("ListAvailable"); - Collection module = ps.Invoke(); - - _configDict.Add("Version", module[0].Properties["Version"].Value.ToString()); - _configDict.Add("ModuleRoot", module[0].Properties["ModuleBase"].Value); - } - - // Serialize Dictionary to JSON - string configJson = JsonConvert.SerializeObject(_configDict, Formatting.Indented); - - File.WriteAllText(Path.Combine(projectFolder, "config.json"), configJson.ToString()); - //WriteObject(_configDict); - PSVariableIntrinsics _sessionState = SessionState.PSVariable; - ps.AddCommand("Set-Variable").AddParameter("Name", "PSSC").AddParameter("Scope", "Script").AddParameter("Value", _configDict); - ps.Invoke(); - WriteObject(_configDict); - - } + WriteError(new ErrorRecord(new System.IO.IOException($"Project directory {projectPath} already exists. Use -Force to overwrite."), "ProjectExists", ErrorCategory.ResourceExists, projectPath)); + return; } } - catch (Exception e) - { - ErrorRecord errorRecord = new(e, $"Could not create directory structure for {Name}.", ErrorCategory.InvalidOperation, null); - ThrowTerminatingError(errorRecord); - } } } diff --git a/src/Project.cs b/src/Project.cs new file mode 100644 index 0000000..1319096 --- /dev/null +++ b/src/Project.cs @@ -0,0 +1,28 @@ +namespace PSSimpleConfig; + +public class Project +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Root { get; set; } + + public Project(string name, string root) + { + Id = Guid.NewGuid(); + Name = name; + Root = root; + } + + public Project(Guid id, string name, string root) + { + Id = id; + Name = name; + Root = root; + } + + public override string ToString() + { + return $"{Name} ({Id})"; + } + +} \ No newline at end of file diff --git a/src/Utilities/PSSimpleConfig.cs b/src/Utilities/PSSimpleConfig.cs index 043de6d..7955ec4 100644 --- a/src/Utilities/PSSimpleConfig.cs +++ b/src/Utilities/PSSimpleConfig.cs @@ -1,87 +1,95 @@ -using System.IO; using System.Management.Automation; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace PSSimpleConfig; -public static class PSSimpleConfig +namespace PSSimpleConfig.Utilities; +public sealed class PSSC { - public static string Scope { get; private set; } = "User"; - public static string Root { get; private set; } - - public static string ProjectRoot { get { return Path.Combine(Root, "projects"); }} - static PSSimpleConfig() + private static readonly object Lock = new object(); + private static PSSC? s_instance = null; + public Dictionary Projects { get; private set; } = new Dictionary(); + public Dictionary ModuleData { get; private set; } = new Dictionary(); + private PSSC() { - UpdateRoot(); - - if (Root == null) { - Root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - } + // Initialize the Projects Dictionary + //Projects = new Dictionary(); + //ModuleData = new Dictionary(); } - public static void SetScope(string configScope) - { - if (configScope == "User" || configScope == "Machine") - { - Scope = configScope; - UpdateRoot(); - } - else - { - throw new ArgumentException("Scope must be either 'User' or 'Machine'."); - } - } - private static void UpdateRoot() + + public static PSSC Instance { - // Allow a user to override the default root folder with environment variable - if (Environment.GetEnvironmentVariable("PSSC_ROOT") != null) - { - Root = Environment.GetEnvironmentVariable("PSSC_ROOT"); - return; - } - else if (Environment.GetEnvironmentVariable("PSSC_ROOT") == null) + get { - string rootFolder = Environment.GetFolderPath( - Scope == "User" ? Environment.SpecialFolder.LocalApplicationData : Environment.SpecialFolder.CommonApplicationData - ); - Root = Path.Combine(rootFolder, "PSSimpleConfig"); + lock (Lock) + { + s_instance ??= new PSSC(); + return s_instance; + } } } - public static FileInfo GetConfigFilePath(string projectName) + public Project GetProjectByName(string name) { - return new FileInfo(Path.Combine(ProjectRoot, projectName, "config.json")); + return Projects.Values.FirstOrDefault(p => p.Name == name); } - public static JObject ImportConfig(string projectName) + public static void InitializeModule(PSCmdlet instance) { - FileInfo configFile = GetConfigFilePath(projectName); - if (!configFile.Exists) - { - throw new FileNotFoundException($"Could not find config file for project {projectName}. Expected file path: {configFile.FullName}"); - } + /// + /// The root path for the PSSimpleConfig module conifguration file. Defaults + /// to the directory containing the module, but if that can't be determined + /// it will default to the CommonApplicationData ($env:ProgramData) folder. + /// + string _moduleCfgRoot = string.Empty; - try { - return JObject.Parse(File.ReadAllText(configFile.FullName)); + /// + /// The full path to the PSSimpleConfig module configuration file. + /// + string _moduleCfgFile = string.Empty; + + /// + /// The root path for the PSSimpleConfig project configuration files. Defaults + /// to the CommonApplicationData ($env:ProgramData) folder. + /// + string _projectCfgRoot = string.Empty; + + /// + /// 1. Check that the default config path exists. Create if it doesn't. + /// 2. Check that the default config file exists. Create if it doesn't. + /// 3. Check for project name collision, throw if there is a name collision. + /// + if (string.IsNullOrEmpty(_moduleCfgRoot)) { + _moduleCfgRoot = Path.GetDirectoryName(instance.MyInvocation?.MyCommand?.Module?.Path) + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "PSSimpleConfig"); } - catch (IOException e) { - throw new IOException($"Could not read config file for project {projectName}. Expected file path: {configFile.FullName}. Error: {e.Message}"); + if (string.IsNullOrEmpty(_moduleCfgFile)) { + _moduleCfgFile = Path.Combine(_moduleCfgRoot, "PSSimpleConfig.json"); } - catch (Exception e) { - throw new Exception($"Could not read config file for project {projectName}. Error: {e.Message}"); + if (string.IsNullOrEmpty(_projectCfgRoot)) { + _projectCfgRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "PSSimpleConfig", "Projects"); } - } - public static void ExportConfig(string projectName, JObject config) - { - FileInfo configFile = GetConfigFilePath(projectName); + if (Directory.Exists(_projectCfgRoot)) { + Directory.CreateDirectory(_projectCfgRoot); + } - try { - File.WriteAllText(configFile.FullName, config.ToString(Formatting.Indented)); + if (!File.Exists(_moduleCfgFile)) { + var moduleCfg = new JObject + { + { "PSSCCfgRoot", _moduleCfgRoot }, + { "PSSCfgFile", _moduleCfgFile }, + { "ProjectRoot", _projectCfgRoot } + }; + File.WriteAllText(_moduleCfgFile, moduleCfg.ToString(Formatting.Indented)); } - catch { - throw new IOException($"Could not write to config file for project {projectName}. Expected file path: {configFile.FullName}"); + + if (Instance.ModuleData.Count > 0) { + Instance.ModuleData.Clear(); + } + var moduleData = JObject.Parse(File.ReadAllText(_moduleCfgFile)); + foreach (var kvp in moduleData) { + Instance.ModuleData.Add(kvp.Key, kvp.Value == null ? string.Empty : kvp.Value.ToString()); } } -} - +} \ No newline at end of file diff --git a/src/Utilities/PSSimpleConfigSingleton.cs b/src/Utilities/PSSimpleConfigSingleton.cs new file mode 100644 index 0000000..846f068 --- /dev/null +++ b/src/Utilities/PSSimpleConfigSingleton.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + + +namespace PSSimpleConfig; +public sealed class PSSimpleConfigSingleton +{ + private static readonly object _lock = new object(); + private static PSSimpleConfigSingleton _instance = null; + + public Dictionary ScopedData { get; private set; } + + private PSSimpleConfigSingleton() + { + // Initialize the ScopedData Dictionary + ScopedData = new Dictionary(); + } + + public static PSSimpleConfigSingleton Instance + { + get + { + lock (_lock) + { + if (_instance == null) + { + _instance = new PSSimpleConfigSingleton(); + } + return _instance; + } + } + } + + public void UpdateScopedData(string guid, JObject projectData) + { + // Here you can add validation or additional logic + ScopedData[guid] = projectData; + } + + public JObject GetScopedData(string guid) + { + if (ScopedData.TryGetValue(guid, out var projectData)) + { + return projectData; + } + return null; + } +}