From 9543c5a2f43918881d02326a9d33a18faeb9f464 Mon Sep 17 00:00:00 2001 From: Axel Kesseler Date: Sun, 2 Jun 2019 06:46:55 +0200 Subject: [PATCH] Initial draft. --- LICENSE => LICENSE.md | 5 +- README.md | 9 +- code/HISTORY.md | 5 + code/README.md | 6 + code/clean.cmd | 41 + code/src/EnvironmentManager.sln | 25 + .../Controls/SplitContainerEx.cs | 123 +++ .../Dialogs/SettingsDialog.Designer.cs | 249 ++++++ .../Dialogs/SettingsDialog.cs | 216 ++++++ .../Dialogs/SettingsDialog.resx | 120 +++ .../Dialogs/VariableDialog.Designer.cs | 256 +++++++ .../Dialogs/VariableDialog.cs | 190 +++++ .../Dialogs/VariableDialog.resx | 126 +++ .../EnvironmentManager.csproj | 170 +++++ .../Exceptions/EnvironmentException.cs | 62 ++ .../Extensions/SettingsExtension.cs | 232 ++++++ .../EnvironmentManager/Images/MainIcon.ico | Bin 0 -> 112591 bytes .../Images/create_32x32.png | Bin 0 -> 899 bytes .../Images/delete_32x32.png | Bin 0 -> 770 bytes .../EnvironmentManager/Images/dump_32x32.png | Bin 0 -> 695 bytes .../EnvironmentManager/Images/exit_32x32.png | Bin 0 -> 828 bytes .../Images/follow_32x32.png | Bin 0 -> 385 bytes .../Images/modify_32x32.png | Bin 0 -> 767 bytes .../EnvironmentManager/Images/save_32x32.png | Bin 0 -> 411 bytes .../Images/settings_32x32.png | Bin 0 -> 733 bytes .../Images/shield_32x32.png | Bin 0 -> 793 bytes .../Internals/PermissionCheck.cs | 83 ++ .../Internals/SelfElevation.cs | 87 +++ .../Internals/TaskScheduler.cs | 174 +++++ .../Internals/WaitCursor.cs | 83 ++ .../EnvironmentManager/MainForm.Designer.cs | 449 +++++++++++ code/src/EnvironmentManager/MainForm.cs | 716 ++++++++++++++++++ code/src/EnvironmentManager/MainForm.resx | 132 ++++ .../Models/CommandArguments.cs | 52 ++ .../Models/EnvironmentVariable.cs | 432 +++++++++++ code/src/EnvironmentManager/Program.cs | 322 ++++++++ .../Properties/AssemblyInfo.cs | 60 ++ .../Properties/Resources.Designer.cs | 163 ++++ .../Properties/Resources.resx | 151 ++++ .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 7 + .../Serializers/EnvironmentSerializer.cs | 136 ++++ code/src/EnvironmentManager/app.config | 6 + code/src/EnvironmentManager/packages.config | 7 + 44 files changed, 4918 insertions(+), 3 deletions(-) rename LICENSE => LICENSE.md (95%) create mode 100644 code/HISTORY.md create mode 100644 code/README.md create mode 100644 code/clean.cmd create mode 100644 code/src/EnvironmentManager.sln create mode 100644 code/src/EnvironmentManager/Controls/SplitContainerEx.cs create mode 100644 code/src/EnvironmentManager/Dialogs/SettingsDialog.Designer.cs create mode 100644 code/src/EnvironmentManager/Dialogs/SettingsDialog.cs create mode 100644 code/src/EnvironmentManager/Dialogs/SettingsDialog.resx create mode 100644 code/src/EnvironmentManager/Dialogs/VariableDialog.Designer.cs create mode 100644 code/src/EnvironmentManager/Dialogs/VariableDialog.cs create mode 100644 code/src/EnvironmentManager/Dialogs/VariableDialog.resx create mode 100644 code/src/EnvironmentManager/EnvironmentManager.csproj create mode 100644 code/src/EnvironmentManager/Exceptions/EnvironmentException.cs create mode 100644 code/src/EnvironmentManager/Extensions/SettingsExtension.cs create mode 100644 code/src/EnvironmentManager/Images/MainIcon.ico create mode 100644 code/src/EnvironmentManager/Images/create_32x32.png create mode 100644 code/src/EnvironmentManager/Images/delete_32x32.png create mode 100644 code/src/EnvironmentManager/Images/dump_32x32.png create mode 100644 code/src/EnvironmentManager/Images/exit_32x32.png create mode 100644 code/src/EnvironmentManager/Images/follow_32x32.png create mode 100644 code/src/EnvironmentManager/Images/modify_32x32.png create mode 100644 code/src/EnvironmentManager/Images/save_32x32.png create mode 100644 code/src/EnvironmentManager/Images/settings_32x32.png create mode 100644 code/src/EnvironmentManager/Images/shield_32x32.png create mode 100644 code/src/EnvironmentManager/Internals/PermissionCheck.cs create mode 100644 code/src/EnvironmentManager/Internals/SelfElevation.cs create mode 100644 code/src/EnvironmentManager/Internals/TaskScheduler.cs create mode 100644 code/src/EnvironmentManager/Internals/WaitCursor.cs create mode 100644 code/src/EnvironmentManager/MainForm.Designer.cs create mode 100644 code/src/EnvironmentManager/MainForm.cs create mode 100644 code/src/EnvironmentManager/MainForm.resx create mode 100644 code/src/EnvironmentManager/Models/CommandArguments.cs create mode 100644 code/src/EnvironmentManager/Models/EnvironmentVariable.cs create mode 100644 code/src/EnvironmentManager/Program.cs create mode 100644 code/src/EnvironmentManager/Properties/AssemblyInfo.cs create mode 100644 code/src/EnvironmentManager/Properties/Resources.Designer.cs create mode 100644 code/src/EnvironmentManager/Properties/Resources.resx create mode 100644 code/src/EnvironmentManager/Properties/Settings.Designer.cs create mode 100644 code/src/EnvironmentManager/Properties/Settings.settings create mode 100644 code/src/EnvironmentManager/Serializers/EnvironmentSerializer.cs create mode 100644 code/src/EnvironmentManager/app.config create mode 100644 code/src/EnvironmentManager/packages.config diff --git a/LICENSE b/LICENSE.md similarity index 95% rename from LICENSE rename to LICENSE.md index df27d6e..b651374 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,6 +1,7 @@ -MIT License -Copyright (c) 2019 Axel Kesseler +# MIT License + +Copyright (c) 2019 plexdata.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0df2f92..58afecb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ + # EnvironmentManager -Managing tool for Windows environment variables. + +Mainly, this program is designed to switch Windows environment variables using the tray icon +menu. But the program also allows to manage (creating, modifying and deleting) all environment +variables. + +Another feature of this program is the possibility to relaunch the program with administrator +privileges as well as to install or remove auto-launch behavior on user logon. diff --git a/code/HISTORY.md b/code/HISTORY.md new file mode 100644 index 0000000..ae3ef76 --- /dev/null +++ b/code/HISTORY.md @@ -0,0 +1,5 @@ + +**1.0.0.0** + +- Initial draft. +- Published on [https://github.com/akesseler/EnvironmentManager](https://github.com/akesseler/EnvironmentManager). diff --git a/code/README.md b/code/README.md new file mode 100644 index 0000000..62d727e --- /dev/null +++ b/code/README.md @@ -0,0 +1,6 @@ + +## Project Build + +Best way to build the whole project is to use _Visual Studio 2017 Community_. Thereafter, +download the complete sources, open the solution file ``EnvironmentManager.sln``, switch +to release and rebuild all. diff --git a/code/clean.cmd b/code/clean.cmd new file mode 100644 index 0000000..603e08e --- /dev/null +++ b/code/clean.cmd @@ -0,0 +1,41 @@ +@echo off + +goto CHOICE_BINARIES + +:CHOICE_BINARIES + +choice /M "Do you really want to remove all \"bin\" and \"obj\" folders" + +if %ERRORLEVEL% == 1 ( + goto CLEAN_BINARIES +) else ( + goto CHOICE_PACKAGES +) + +:CLEAN_BINARIES + +echo Clean up all "bin" and "obj" folders... + +for /d /r %%x in (bin, obj) do rmdir "%%x" /s /q 2> nul + +:CHOICE_PACKAGES + +choice /M "Do you really want to remove all folders in \"packages\"" + +if %ERRORLEVEL% == 1 ( + goto CLEAN_PACKAGES +) else ( + goto CHOICE_FINISHED +) + +:CLEAN_PACKAGES + +echo Clean up all "packages"... + +for /d /r %%x in (packages) do rmdir "%%x" /s /q 2> nul + +:CHOICE_FINISHED + +echo Done! + +pause diff --git a/code/src/EnvironmentManager.sln b/code/src/EnvironmentManager.sln new file mode 100644 index 0000000..1e93da2 --- /dev/null +++ b/code/src/EnvironmentManager.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.645 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvironmentManager", "EnvironmentManager\EnvironmentManager.csproj", "{C1467758-5E28-4BA1-B416-D8E4685A5609}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C1467758-5E28-4BA1-B416-D8E4685A5609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1467758-5E28-4BA1-B416-D8E4685A5609}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1467758-5E28-4BA1-B416-D8E4685A5609}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1467758-5E28-4BA1-B416-D8E4685A5609}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E763ED87-E880-43D3-8A13-070E62875753} + EndGlobalSection +EndGlobal diff --git a/code/src/EnvironmentManager/Controls/SplitContainerEx.cs b/code/src/EnvironmentManager/Controls/SplitContainerEx.cs new file mode 100644 index 0000000..5537ed4 --- /dev/null +++ b/code/src/EnvironmentManager/Controls/SplitContainerEx.cs @@ -0,0 +1,123 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using System.Drawing; +using System.Diagnostics; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace Plexdata.EnvironmentManager.Controls +{ + // Find a quite nice example of a splitter based on a TableLayoutPanel... + // http://stackoverflow.com/questions/5033690/add-button-controls-to-splitcontainer-splitter/5046984#5046984 + + public class SplitContainerEx : SplitContainer + { + public SplitContainerEx() + : base() + { + this.SetStyle(ControlStyles.DoubleBuffer, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + + base.TabStop = false; + } + + protected override void OnKeyUp(KeyEventArgs args) + { + base.OnKeyUp(args); + // Needed to disable painted focus rectangle... + this.Refresh(); + } + + protected override void OnPaint(PaintEventArgs args) + { + try + { + Size size = new Size(4, 40); // Gripper dimension... + Rectangle rect = this.SplitterRectangle; + VisualStyleRenderer renderer = null; + + if (this.Orientation == Orientation.Vertical) + { + rect.Y = (rect.Bottom - (rect.Top + size.Height)) / 2; + rect.Height = size.Height; + rect.Width = size.Width; + rect.X += Math.Max((this.SplitterWidth - rect.Width) / 2, 0) - 1; + + if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.Rebar.Gripper.Normal)) + { + renderer = new VisualStyleRenderer(VisualStyleElement.Rebar.Gripper.Normal); + } + } + else + { + rect.X = (rect.Right - (rect.Left + size.Height)) / 2; + rect.Height = size.Width; + rect.Width = size.Height; + rect.Y += Math.Max((this.SplitterWidth - rect.Height) / 2, 0) - 1; + + if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.Rebar.GripperVertical.Normal)) + { + renderer = new VisualStyleRenderer(VisualStyleElement.Rebar.GripperVertical.Normal); + } + } + + if (renderer != null) + { + renderer.DrawBackground(args.Graphics, rect, args.ClipRectangle); + } + + if (base.Focused && base.TabStop) + { + ControlPaint.DrawFocusRectangle(args.Graphics, + Rectangle.Inflate(this.SplitterRectangle, -1, -1), + this.ForeColor, this.BackColor); + } + } + catch (Exception exception) + { + Debug.WriteLine(exception); + } + } + + protected override void OnDoubleClick(EventArgs args) + { + base.OnDoubleClick(args); + if (this.Orientation == Orientation.Vertical) + { + this.SplitterDistance = this.ClientSize.Width / 2; + } + else + { + this.SplitterDistance = this.ClientSize.Height / 2; + } + this.Refresh(); + } + } +} + diff --git a/code/src/EnvironmentManager/Dialogs/SettingsDialog.Designer.cs b/code/src/EnvironmentManager/Dialogs/SettingsDialog.Designer.cs new file mode 100644 index 0000000..dae25c8 --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/SettingsDialog.Designer.cs @@ -0,0 +1,249 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Plexdata.EnvironmentManager.Dialogs +{ + partial class SettingsDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnCancel = new System.Windows.Forms.Button(); + this.btnAccept = new System.Windows.Forms.Button(); + this.btnRemove = new System.Windows.Forms.Button(); + this.btnInstall = new System.Windows.Forms.Button(); + this.lblRemove = new System.Windows.Forms.Label(); + this.lblInstall = new System.Windows.Forms.Label(); + this.tblRegistration = new System.Windows.Forms.TableLayoutPanel(); + this.tabContent = new System.Windows.Forms.TabControl(); + this.tabRegistration = new System.Windows.Forms.TabPage(); + this.tabArguments = new System.Windows.Forms.TabPage(); + this.txtArguments = new System.Windows.Forms.TextBox(); + this.tblRegistration.SuspendLayout(); + this.tabContent.SuspendLayout(); + this.tabRegistration.SuspendLayout(); + this.tabArguments.SuspendLayout(); + this.SuspendLayout(); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(397, 277); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 2; + this.btnCancel.Text = "&Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // btnAccept + // + this.btnAccept.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnAccept.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnAccept.Location = new System.Drawing.Point(316, 277); + this.btnAccept.Name = "btnAccept"; + this.btnAccept.Size = new System.Drawing.Size(75, 23); + this.btnAccept.TabIndex = 1; + this.btnAccept.Text = "&OK"; + this.btnAccept.UseVisualStyleBackColor = true; + // + // btnRemove + // + this.btnRemove.Location = new System.Drawing.Point(344, 104); + this.btnRemove.Name = "btnRemove"; + this.btnRemove.Size = new System.Drawing.Size(75, 23); + this.btnRemove.TabIndex = 3; + this.btnRemove.Text = "&Remove"; + this.btnRemove.UseVisualStyleBackColor = true; + this.btnRemove.Click += new System.EventHandler(this.OnRemoveClicked); + // + // btnInstall + // + this.btnInstall.Location = new System.Drawing.Point(344, 3); + this.btnInstall.Name = "btnInstall"; + this.btnInstall.Size = new System.Drawing.Size(75, 23); + this.btnInstall.TabIndex = 1; + this.btnInstall.Text = "&Install"; + this.btnInstall.UseVisualStyleBackColor = true; + this.btnInstall.Click += new System.EventHandler(this.OnInstallClicked); + // + // lblRemove + // + this.lblRemove.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblRemove.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblRemove.Location = new System.Drawing.Point(3, 101); + this.lblRemove.Name = "lblRemove"; + this.lblRemove.Size = new System.Drawing.Size(335, 102); + this.lblRemove.TabIndex = 2; + this.lblRemove.Text = "Click button [Remove] to delete program registration for auto-launch on user logi" + + "n."; + // + // lblInstall + // + this.lblInstall.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblInstall.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblInstall.Location = new System.Drawing.Point(3, 0); + this.lblInstall.Name = "lblInstall"; + this.lblInstall.Size = new System.Drawing.Size(335, 101); + this.lblInstall.TabIndex = 0; + this.lblInstall.Text = "Click button [Install] to register program for auto-launch on user login."; + // + // tblRegistration + // + this.tblRegistration.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tblRegistration.ColumnCount = 2; + this.tblRegistration.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tblRegistration.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tblRegistration.Controls.Add(this.btnRemove, 1, 1); + this.tblRegistration.Controls.Add(this.lblInstall, 0, 0); + this.tblRegistration.Controls.Add(this.btnInstall, 1, 0); + this.tblRegistration.Controls.Add(this.lblRemove, 0, 1); + this.tblRegistration.Location = new System.Drawing.Point(15, 15); + this.tblRegistration.Margin = new System.Windows.Forms.Padding(10); + this.tblRegistration.Name = "tblRegistration"; + this.tblRegistration.RowCount = 2; + this.tblRegistration.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tblRegistration.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tblRegistration.Size = new System.Drawing.Size(422, 203); + this.tblRegistration.TabIndex = 0; + // + // tabContent + // + this.tabContent.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabContent.Controls.Add(this.tabRegistration); + this.tabContent.Controls.Add(this.tabArguments); + this.tabContent.Location = new System.Drawing.Point(12, 12); + this.tabContent.Name = "tabContent"; + this.tabContent.SelectedIndex = 0; + this.tabContent.Size = new System.Drawing.Size(460, 259); + this.tabContent.TabIndex = 0; + // + // tabRegistration + // + this.tabRegistration.Controls.Add(this.tblRegistration); + this.tabRegistration.Location = new System.Drawing.Point(4, 22); + this.tabRegistration.Name = "tabRegistration"; + this.tabRegistration.Padding = new System.Windows.Forms.Padding(5); + this.tabRegistration.Size = new System.Drawing.Size(452, 233); + this.tabRegistration.TabIndex = 0; + this.tabRegistration.Text = "Registration"; + this.tabRegistration.UseVisualStyleBackColor = true; + // + // tabArguments + // + this.tabArguments.Controls.Add(this.txtArguments); + this.tabArguments.Location = new System.Drawing.Point(4, 22); + this.tabArguments.Name = "tabArguments"; + this.tabArguments.Padding = new System.Windows.Forms.Padding(5); + this.tabArguments.Size = new System.Drawing.Size(452, 233); + this.tabArguments.TabIndex = 1; + this.tabArguments.Text = "Arguments"; + this.tabArguments.UseVisualStyleBackColor = true; + // + // txtArguments + // + this.txtArguments.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtArguments.BackColor = System.Drawing.SystemColors.Window; + this.txtArguments.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtArguments.Location = new System.Drawing.Point(15, 15); + this.txtArguments.Margin = new System.Windows.Forms.Padding(10); + this.txtArguments.Multiline = true; + this.txtArguments.Name = "txtArguments"; + this.txtArguments.ReadOnly = true; + this.txtArguments.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtArguments.Size = new System.Drawing.Size(422, 203); + this.txtArguments.TabIndex = 0; + this.txtArguments.WordWrap = false; + // + // SettingsDialog + // + this.AcceptButton = this.btnAccept; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(484, 312); + this.Controls.Add(this.tabContent); + this.Controls.Add(this.btnAccept); + this.Controls.Add(this.btnCancel); + this.DoubleBuffered = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(390, 300); + this.Name = "SettingsDialog"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Settings"; + this.tblRegistration.ResumeLayout(false); + this.tabContent.ResumeLayout(false); + this.tabRegistration.ResumeLayout(false); + this.tabArguments.ResumeLayout(false); + this.tabArguments.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnAccept; + private System.Windows.Forms.Button btnInstall; + private System.Windows.Forms.Label lblInstall; + private System.Windows.Forms.Button btnRemove; + private System.Windows.Forms.Label lblRemove; + private System.Windows.Forms.TableLayoutPanel tblRegistration; + private System.Windows.Forms.TabControl tabContent; + private System.Windows.Forms.TabPage tabRegistration; + private System.Windows.Forms.TabPage tabArguments; + private System.Windows.Forms.TextBox txtArguments; + } +} \ No newline at end of file diff --git a/code/src/EnvironmentManager/Dialogs/SettingsDialog.cs b/code/src/EnvironmentManager/Dialogs/SettingsDialog.cs new file mode 100644 index 0000000..f1cc789 --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/SettingsDialog.cs @@ -0,0 +1,216 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.ArgumentParser.Extensions; +using Plexdata.EnvironmentManager.Extensions; +using Plexdata.EnvironmentManager.Internals; +using Plexdata.LogWriter.Extensions; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Dialogs +{ + public partial class SettingsDialog : Form + { + public SettingsDialog() + { + this.InitializeComponent(); + this.Icon = Properties.Resources.MainIcon; + + PermissionCheck.SetButtonShield(this.btnRemove, !PermissionCheck.IsRunAsAdmin); + PermissionCheck.SetButtonShield(this.btnInstall, !PermissionCheck.IsRunAsAdmin); + + this.SetDoubleBuffered(this.tabContent); + this.SetDoubleBuffered(this.tabRegistration); + this.SetDoubleBuffered(this.tabArguments); + this.SetDoubleBuffered(this.txtArguments); // Actually, it does not really has an effect. But why? + } + + protected override void OnLoad(EventArgs args) + { + base.OnLoad(args); + this.txtArguments.Text = Program.Arguments.Generate(); + } + + protected override void OnShown(EventArgs args) + { + using (new WaitCursor(this)) + { + this.LoadSettings(); + base.OnShown(args); + this.UpdateButtons(); + } + } + + protected override void OnClosing(CancelEventArgs args) + { + base.OnClosing(args); + this.SaveSettings(); + } + + private void OnInstallClicked(Object sender, EventArgs args) + { + using (new WaitCursor(this)) + { + this.Enabled = false; + + try + { + this.ExecuteCommand("--install", !PermissionCheck.IsRunAsAdmin); + } + catch (Exception exception) + { + Program.Logger.Critical("Unexpected error while executing task create command.", exception); + } + finally + { + this.Enabled = true; + } + + this.UpdateButtons(); + } + } + + private void OnRemoveClicked(Object sender, EventArgs args) + { + using (new WaitCursor(this)) + { + this.Enabled = false; + + try + { + this.ExecuteCommand("--remove", !PermissionCheck.IsRunAsAdmin); + } + catch (Exception exception) + { + Program.Logger.Critical("Unexpected error while executing task remove command.", exception); + } + finally + { + this.Enabled = true; + } + + this.UpdateButtons(); + } + } + + private void UpdateButtons() + { + try + { + Boolean installed = TaskScheduler.IsInstalled(); + + this.btnRemove.Enabled = installed; + this.btnInstall.Enabled = !installed; + } + catch (Exception exception) + { + Program.Logger.Critical("Unexpected error while updating buttons.", exception); + + this.btnRemove.Enabled = false; + this.btnInstall.Enabled = false; + } + + this.Update(); + } + + private Boolean ExecuteCommand(String arguments, Boolean elevated) + { + try + { + ProcessStartInfo info = new ProcessStartInfo + { + Verb = elevated ? "runas" : String.Empty, + Arguments = arguments ?? String.Empty, + FileName = Assembly.GetExecutingAssembly().Location, + WindowStyle = ProcessWindowStyle.Hidden + }; + + using (Process process = Process.Start(info)) + { + process.WaitForExit(); + return process.ExitCode == 0; + } + } + catch (Win32Exception exception) + { + const Int32 ERROR_CANCELLED = 1223; + + if (exception.NativeErrorCode == ERROR_CANCELLED) + { + Program.Logger.Warning("User rejected command execution in settings dialog.", new (String, Object)[] { ("Arguments", arguments), ("Elevated", elevated) }); + return false; + } + else + { + Program.Logger.Critical("Unexpected error while command execution in settings dialog.", exception); + throw exception; + } + } + } + + private void SetDoubleBuffered(Control control) + { + typeof(Control).InvokeMember("DoubleBuffered", + BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, + null, control, new Object[] { true }); + } + + private void LoadSettings() + { + try + { + this.DesktopBounds = Program.ReadSettingsValue(this.GetType().Name, nameof(this.DesktopBounds)) + .StringToBounds(this.StandardBounds(new Size(500, 350))); + } + catch (Exception exception) + { + Program.Logger.Error("Loading settings for settings dialog from configuration has failed.", exception); + } + + this.EnsureScreenLocation(); + } + + private void SaveSettings() + { + try + { + Program.SaveSettingsValue(this.GetType().Name, nameof(this.DesktopBounds), this.DesktopBounds.BoundsToString()); + Program.SaveSettings(); + } + catch (Exception exception) + { + Program.Logger.Error("Saving settings for settings dialog into configuration has failed.", exception); + } + } + } +} + + + + diff --git a/code/src/EnvironmentManager/Dialogs/SettingsDialog.resx b/code/src/EnvironmentManager/Dialogs/SettingsDialog.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/SettingsDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/Dialogs/VariableDialog.Designer.cs b/code/src/EnvironmentManager/Dialogs/VariableDialog.Designer.cs new file mode 100644 index 0000000..5558b8d --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/VariableDialog.Designer.cs @@ -0,0 +1,256 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Plexdata.EnvironmentManager.Dialogs +{ + partial class VariableDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnAccept = new System.Windows.Forms.Button(); + this.valScope = new System.Windows.Forms.ComboBox(); + this.lblScope = new System.Windows.Forms.Label(); + this.valLabel = new System.Windows.Forms.TextBox(); + this.valValue = new System.Windows.Forms.TextBox(); + this.lblLabel = new System.Windows.Forms.Label(); + this.lblValue = new System.Windows.Forms.Label(); + this.labelError = new System.Windows.Forms.ErrorProvider(this.components); + this.valueError = new System.Windows.Forms.ErrorProvider(this.components); + this.valShift = new System.Windows.Forms.TextBox(); + this.lblShift = new System.Windows.Forms.Label(); + this.tabLayout = new System.Windows.Forms.TableLayoutPanel(); + ((System.ComponentModel.ISupportInitialize)(this.labelError)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.valueError)).BeginInit(); + this.tabLayout.SuspendLayout(); + this.SuspendLayout(); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(297, 327); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 2; + this.btnCancel.Text = "&Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // btnAccept + // + this.btnAccept.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnAccept.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnAccept.Location = new System.Drawing.Point(216, 327); + this.btnAccept.Name = "btnAccept"; + this.btnAccept.Size = new System.Drawing.Size(75, 23); + this.btnAccept.TabIndex = 1; + this.btnAccept.Text = "&OK"; + this.btnAccept.UseVisualStyleBackColor = true; + // + // valScope + // + this.valScope.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valScope.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.valScope.FormattingEnabled = true; + this.valScope.Location = new System.Drawing.Point(63, 3); + this.valScope.Name = "valScope"; + this.valScope.Size = new System.Drawing.Size(294, 21); + this.valScope.TabIndex = 1; + // + // lblScope + // + this.lblScope.AutoSize = true; + this.lblScope.Location = new System.Drawing.Point(3, 0); + this.lblScope.Name = "lblScope"; + this.lblScope.Size = new System.Drawing.Size(41, 13); + this.lblScope.TabIndex = 0; + this.lblScope.Text = "&Scope:"; + // + // valLabel + // + this.valLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valLabel.Location = new System.Drawing.Point(63, 30); + this.valLabel.Name = "valLabel"; + this.valLabel.Size = new System.Drawing.Size(294, 20); + this.valLabel.TabIndex = 3; + // + // valValue + // + this.valValue.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valValue.Location = new System.Drawing.Point(63, 56); + this.valValue.Multiline = true; + this.valValue.Name = "valValue"; + this.valValue.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.valValue.Size = new System.Drawing.Size(294, 122); + this.valValue.TabIndex = 5; + this.valValue.WordWrap = false; + // + // lblLabel + // + this.lblLabel.AutoSize = true; + this.lblLabel.Location = new System.Drawing.Point(3, 27); + this.lblLabel.Name = "lblLabel"; + this.lblLabel.Size = new System.Drawing.Size(33, 13); + this.lblLabel.TabIndex = 2; + this.lblLabel.Text = "&Label"; + // + // lblValue + // + this.lblValue.AutoSize = true; + this.lblValue.Location = new System.Drawing.Point(3, 53); + this.lblValue.Name = "lblValue"; + this.lblValue.Size = new System.Drawing.Size(34, 13); + this.lblValue.TabIndex = 4; + this.lblValue.Text = "&Value"; + // + // labelError + // + this.labelError.ContainerControl = this; + // + // valueError + // + this.valueError.ContainerControl = this; + // + // valShift + // + this.valShift.AcceptsReturn = true; + this.valShift.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valShift.Location = new System.Drawing.Point(63, 184); + this.valShift.Multiline = true; + this.valShift.Name = "valShift"; + this.valShift.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.valShift.Size = new System.Drawing.Size(294, 122); + this.valShift.TabIndex = 7; + this.valShift.WordWrap = false; + // + // lblShift + // + this.lblShift.AutoSize = true; + this.lblShift.Location = new System.Drawing.Point(3, 181); + this.lblShift.Name = "lblShift"; + this.lblShift.Size = new System.Drawing.Size(28, 13); + this.lblShift.TabIndex = 6; + this.lblShift.Text = "S&hift"; + // + // tabLayout + // + this.tabLayout.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabLayout.ColumnCount = 2; + this.tabLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 60F)); + this.tabLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tabLayout.Controls.Add(this.valScope, 1, 0); + this.tabLayout.Controls.Add(this.valValue, 1, 2); + this.tabLayout.Controls.Add(this.valShift, 1, 3); + this.tabLayout.Controls.Add(this.lblScope, 0, 0); + this.tabLayout.Controls.Add(this.valLabel, 1, 1); + this.tabLayout.Controls.Add(this.lblShift, 0, 3); + this.tabLayout.Controls.Add(this.lblLabel, 0, 1); + this.tabLayout.Controls.Add(this.lblValue, 0, 2); + this.tabLayout.Location = new System.Drawing.Point(12, 12); + this.tabLayout.Name = "tabLayout"; + this.tabLayout.RowCount = 4; + this.tabLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tabLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tabLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tabLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tabLayout.Size = new System.Drawing.Size(360, 309); + this.tabLayout.TabIndex = 0; + // + // VariableDialog + // + this.AcceptButton = this.btnAccept; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(384, 362); + this.Controls.Add(this.tabLayout); + this.Controls.Add(this.btnAccept); + this.Controls.Add(this.btnCancel); + this.DoubleBuffered = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(400, 400); + this.Name = "VariableDialog"; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Environment Variable"; + ((System.ComponentModel.ISupportInitialize)(this.labelError)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.valueError)).EndInit(); + this.tabLayout.ResumeLayout(false); + this.tabLayout.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnAccept; + private System.Windows.Forms.ComboBox valScope; + private System.Windows.Forms.Label lblScope; + private System.Windows.Forms.TextBox valLabel; + private System.Windows.Forms.TextBox valValue; + private System.Windows.Forms.Label lblLabel; + private System.Windows.Forms.Label lblValue; + private System.Windows.Forms.ErrorProvider labelError; + private System.Windows.Forms.ErrorProvider valueError; + private System.Windows.Forms.TextBox valShift; + private System.Windows.Forms.TableLayoutPanel tabLayout; + private System.Windows.Forms.Label lblShift; + } +} \ No newline at end of file diff --git a/code/src/EnvironmentManager/Dialogs/VariableDialog.cs b/code/src/EnvironmentManager/Dialogs/VariableDialog.cs new file mode 100644 index 0000000..e24330c --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/VariableDialog.cs @@ -0,0 +1,190 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.EnvironmentManager.Extensions; +using Plexdata.EnvironmentManager.Internals; +using Plexdata.EnvironmentManager.Models; +using Plexdata.LogWriter.Extensions; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Dialogs +{ + public partial class VariableDialog : Form + { + public VariableDialog(EnvironmentVariableTarget scope) + : base() + { + this.InitializeComponent(); + this.Icon = Properties.Resources.MainIcon; + this.Variable = new EnvironmentVariable(scope); + } + + public VariableDialog(EnvironmentVariable variable) + : base() + { + this.InitializeComponent(); + this.Icon = Properties.Resources.MainIcon; + if (variable == null) { throw new ArgumentNullException(nameof(variable)); } + this.Variable = (EnvironmentVariable)variable.Clone(); + } + + public EnvironmentVariable Variable { get; private set; } + + protected override void OnLoad(EventArgs args) + { + base.OnLoad(args); + + this.Text += this.Variable.IsCreated ? " (NEW)" : " (EDIT)"; + + this.valScope.DataSource = Enum.GetValues(this.Variable.Scope.GetType()); + this.valScope.SelectedItem = this.Variable.Scope; + this.valScope.Enabled = false; + + this.valLabel.Text = this.Variable.Label; + this.valLabel.TextChanged += this.OnLabelTextChanged; + this.valLabel.ReadOnly = this.Variable.IsDeleted; + this.valLabel.BackColor = this.valLabel.ReadOnly ? SystemColors.Info : SystemColors.Window; + + this.valValue.Text = this.Variable.Value; + this.valValue.TextChanged += this.OnValueTextChanged; + this.valValue.ReadOnly = this.Variable.IsDeleted; + this.valValue.BackColor = this.valValue.ReadOnly ? SystemColors.Info : SystemColors.Window; + + this.valShift.Lines = this.Variable.Shift; + this.valShift.TextChanged += this.OnShiftTextChanged; + this.valShift.ReadOnly = this.Variable.IsDeleted; + this.valShift.BackColor = this.valShift.ReadOnly ? SystemColors.Info : SystemColors.Window; + + this.btnAccept.Enabled = this.CanEnableAccept(); + this.btnAccept.Click += this.OnAcceptClicked; + } + + protected override void OnShown(EventArgs args) + { + using (new WaitCursor(this)) + { + this.LoadSettings(); + base.OnShown(args); + } + } + + protected override void OnClosing(CancelEventArgs args) + { + base.OnClosing(args); + this.SaveSettings(); + } + + private void OnLabelTextChanged(Object sender, EventArgs args) + { + if (!String.IsNullOrWhiteSpace(this.valLabel.Text)) + { + this.labelError.SetError(this.valLabel, String.Empty); + } + else + { + this.labelError.SetIconPadding(this.valLabel, 2); + this.labelError.SetIconAlignment(this.valLabel, ErrorIconAlignment.MiddleLeft); + this.labelError.SetError(this.valLabel, "The label for an environment variable is required.!"); + } + + this.btnAccept.Enabled = this.CanEnableAccept(); + } + + private void OnValueTextChanged(Object sender, EventArgs args) + { + if (!String.IsNullOrWhiteSpace(this.valValue.Text)) + { + this.valueError.SetError(this.valValue, String.Empty); + } + else + { + this.valueError.SetIconPadding(this.valValue, 2); + this.valueError.SetIconAlignment(this.valValue, ErrorIconAlignment.TopLeft); + this.valueError.SetError(this.valValue, "The value for an environment variable is required.!"); + } + + this.btnAccept.Enabled = this.CanEnableAccept(); + } + + private void OnShiftTextChanged(Object sender, EventArgs args) + { + this.btnAccept.Enabled = this.CanEnableAccept(); + } + + private void OnAcceptClicked(Object sender, EventArgs args) + { + if (String.IsNullOrWhiteSpace(this.valLabel.Text) || String.IsNullOrWhiteSpace(this.valValue.Text)) + { + base.DialogResult = DialogResult.None; + return; + } + + this.Variable.Scope = (EnvironmentVariableTarget)this.valScope.SelectedItem; + this.Variable.Label = this.valLabel.Text; + this.Variable.Value = this.valValue.Text; + + this.Variable.Shift = this.valShift.Lines; + } + + private Boolean CanEnableAccept() + { + return + !this.Variable.IsReadonly && + !this.Variable.IsDeleted && + !String.IsNullOrWhiteSpace(this.valLabel.Text) && + !String.IsNullOrWhiteSpace(this.valValue.Text); + } + + private void LoadSettings() + { + try + { + this.DesktopBounds = Program.ReadSettingsValue(this.GetType().Name, nameof(this.DesktopBounds)) + .StringToBounds(this.StandardBounds(new Size(400, 400))); + } + catch (Exception exception) + { + Program.Logger.Error("Loading settings for variable dialog from configuration has failed.", exception); + } + + this.EnsureScreenLocation(); + } + + private void SaveSettings() + { + try + { + Program.SaveSettingsValue(this.GetType().Name, nameof(this.DesktopBounds), this.DesktopBounds.BoundsToString()); + Program.SaveSettings(); + } + catch (Exception exception) + { + Program.Logger.Error("Saving settings for variable dialog into configuration has failed.", exception); + } + } + } +} diff --git a/code/src/EnvironmentManager/Dialogs/VariableDialog.resx b/code/src/EnvironmentManager/Dialogs/VariableDialog.resx new file mode 100644 index 0000000..8a3010a --- /dev/null +++ b/code/src/EnvironmentManager/Dialogs/VariableDialog.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 121, 17 + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/EnvironmentManager.csproj b/code/src/EnvironmentManager/EnvironmentManager.csproj new file mode 100644 index 0000000..b32e560 --- /dev/null +++ b/code/src/EnvironmentManager/EnvironmentManager.csproj @@ -0,0 +1,170 @@ + + + + + Debug + AnyCPU + {C1467758-5E28-4BA1-B416-D8E4685A5609} + WinExe + Plexdata.EnvironmentManager + pdenvmgr + v4.7.2 + 512 + true + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.0 + false + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + Images\MainIcon.ico + + + + ..\packages\Plexdata.ArgumentParser.NET.1.0.0.12\lib\net472\Plexdata.ArgumentParser.NET.dll + + + ..\packages\Plexdata.CfgParser.NET.1.0.0.1\lib\net472\Plexdata.CfgParser.NET.dll + + + ..\packages\Plexdata.LogWriter.Abstraction.1.0.2.4\lib\netstandard2.0\Plexdata.LogWriter.Abstraction.dll + + + ..\packages\Plexdata.LogWriter.Persistent.1.0.2.4\lib\netstandard2.0\Plexdata.LogWriter.Persistent.dll + + + + + + + + + + + + + + + + Component + + + Form + + + SettingsDialog.cs + + + Form + + + VariableDialog.cs + + + + + + + + + Form + + + MainForm.cs + + + + + + + + SettingsDialog.cs + + + VariableDialog.cs + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4.7.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/Exceptions/EnvironmentException.cs b/code/src/EnvironmentManager/Exceptions/EnvironmentException.cs new file mode 100644 index 0000000..8caa208 --- /dev/null +++ b/code/src/EnvironmentManager/Exceptions/EnvironmentException.cs @@ -0,0 +1,62 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.EnvironmentManager.Models; +using System; + +namespace Plexdata.EnvironmentManager.Exceptions +{ + public class EnvironmentException : Exception + { + public EnvironmentException() + : base() + { + this.Variable = null; + } + + public EnvironmentException(EnvironmentVariable variable, String message) + : base(message) + { + this.Variable = variable; + } + + public EnvironmentException(EnvironmentVariable variable, Exception exception) + : base(exception != null ? exception.Message : String.Empty, exception) + { + this.Variable = variable; + } + + public EnvironmentVariable Variable { get; private set; } + + public override String ToString() + { + return + $"{base.Message}" + + $"{Environment.NewLine}" + + $"{(this.Variable != null ? this.Variable.ToString() : "")}" + + $"{Environment.NewLine}" + + $"{base.StackTrace}"; + } + } +} diff --git a/code/src/EnvironmentManager/Extensions/SettingsExtension.cs b/code/src/EnvironmentManager/Extensions/SettingsExtension.cs new file mode 100644 index 0000000..679b5a7 --- /dev/null +++ b/code/src/EnvironmentManager/Extensions/SettingsExtension.cs @@ -0,0 +1,232 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Extensions +{ + public static class SettingsExtension + { + private static Char[] Delimiters = new Char[] { ',', ';', ':' }; + private static Char Delimiter = SettingsExtension.Delimiters[0]; + + public static Rectangle StandardBounds(this Form form) + { + if (form == null) + { + throw new ArgumentNullException(); + } + + return form.StandardBounds(form.Size); + } + + public static Rectangle StandardBounds(this Form form, Size suggestion) + { + if (form == null) + { + throw new ArgumentNullException(); + } + + Size minimumSize = form.MinimumSize; + Size currentSize = suggestion; + Rectangle workingArea = Screen.PrimaryScreen.WorkingArea; + + Int32 w = SettingsExtension.ApplyLimitations(currentSize.Width, minimumSize.Width, workingArea.Width); + Int32 h = SettingsExtension.ApplyLimitations(currentSize.Height, minimumSize.Height, workingArea.Height); + + Size s = new Size(w, h); + Point p = SettingsExtension.ScreenCentered(s); + + return new Rectangle(p, s); + } + + public static void EnsureScreenLocation(this Form form) + { + if (form == null) + { + return; + } + + Rectangle bounds = Screen.PrimaryScreen.WorkingArea; + Point location = form.Location; + Size size = form.Size; + + foreach (Screen screen in Screen.AllScreens) + { + if (screen.Bounds.Contains(new Rectangle(location, size))) + { + return; // Location is still within bounds. + } + } + + // Adjust X location to be on the primary screen! + + Int32 x = 0; + if (location.X < bounds.Left) + { + x = bounds.Left; + } + else if (location.X + size.Width > bounds.Left + bounds.Right) + { + x = bounds.Right - size.Width; + } + else + { + x = location.X; + } + + // Adjust Y location to be on the primary screen! + + Int32 y = 0; + if (location.Y < bounds.Top) + { + y = bounds.Top; + } + else if (location.Y + size.Height > bounds.Top + bounds.Bottom) + { + y = bounds.Bottom - size.Height; + } + else + { + y = location.Y; + } + + form.Location = new Point(x, y); + } + + public static Point ScreenCentered(this Size size) + { + Rectangle bounds = Screen.PrimaryScreen.WorkingArea; + + return new Point( + (bounds.Width - SettingsExtension.ApplyLimitations(size.Width, 0, bounds.Width)) / 2, + (bounds.Height - SettingsExtension.ApplyLimitations(size.Height, 0, bounds.Height)) / 2 + ); + } + + public static Boolean IsValid(this Rectangle value) + { + return value != null && value.Left >= 0 && value.Top >= 0 && value.Width > 0 && value.Height > 0; + } + + public static Rectangle StringToBounds(this String value, Rectangle standard) + { + if (!standard.IsValid()) + { + throw new ArgumentOutOfRangeException(); + } + + Rectangle result = standard; + + if (String.IsNullOrWhiteSpace(value)) + { + return result; + } + + String[] array = value.Split(SettingsExtension.Delimiters, StringSplitOptions.None); + + if (array.Length > 0 && Int32.TryParse(array[0].Trim(), out Int32 x)) + { + result.X = x; + } + else + { + result.X = standard.X; + } + + if (array.Length > 1 && Int32.TryParse(array[1].Trim(), out Int32 y)) + { + result.Y = y; + } + else + { + result.Y = standard.Y; + } + + if (array.Length > 2 && Int32.TryParse(array[2].Trim(), out Int32 w)) + { + result.Width = w; + } + else + { + result.Width = standard.Width; + } + + if (array.Length > 3 && Int32.TryParse(array[3].Trim(), out Int32 h)) + { + result.Height = h; + } + else + { + result.Height = standard.Height; + } + + return result; + } + + public static String BoundsToString(this Rectangle value) + { + if (!value.IsValid()) + { + return String.Empty; + } + + return $"{value.X}{SettingsExtension.Delimiter}{value.Y}{SettingsExtension.Delimiter}{value.Width}{SettingsExtension.Delimiter}{value.Height}"; + } + + public static Int32 StringToInteger(this String value, Int32 lower, Int32 upper, Int32 standard) + { + if (Int32.TryParse(value, out Int32 result)) + { + return SettingsExtension.ApplyLimitations(result, lower, upper); + } + else + { + return standard; + } + } + + public static String IntegerToString(this Int32 value) + { + return value.ToString(); + } + + private static Int32 ApplyLimitations(Int32 value, Int32 lower, Int32 upper) + { + if (value < lower) + { + return lower; + } + + if (value > upper) + { + return upper; + } + + return value; + } + } +} diff --git a/code/src/EnvironmentManager/Images/MainIcon.ico b/code/src/EnvironmentManager/Images/MainIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9c5c7e41647e6c993d071dc74aaef0f73bd3e923 GIT binary patch literal 112591 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%0D@Pebv`?vqMb;`)OL@YoG*&S9upc-v1B z;Eq?alscg>o15J5Ktd=YhoiyignT~H*k`s=3XS0t6I2fKj})=rcmuOk#D@!yXaXHiAe1srqp`+!}gD%wgg@(aKY zCpw(oknu%yl29(i*a6(i5sobKMnWOt$sZUOkiRG`5c2uioJHwALd>T-@gT4|vnY)W zECp@?^D^ug2r2bCA+e-)dlTW3*-SWUo)|(!yg|~_crf#2WDk>TWWNon!4qJMq}N_h z>SZ(ttR!s8a&=)PjGB#=S>*8V9;csyOZ?5_G{cd4LVVNb`9j8GT>xG(_B`*GG?Hb< Z<2M%48vlL!C+ literal 0 HcmV?d00001 diff --git a/code/src/EnvironmentManager/Images/delete_32x32.png b/code/src/EnvironmentManager/Images/delete_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..fb82b6a2621841a23f86bbf8458ca2bf38a3e8ba GIT binary patch literal 770 zcmV+d1O5DoP) z&du)5R3s4!Ndv$za2z-awA;`R;482Q%uD(b%2_FMK+-fR)CoZam;oGSTT-VWJW;Je&7*sIYP7uv%qypJL!R@2uL~zyxT)@jO(p&=R064 zp94;MpVxrx7DTs!n)iMRm@|!R2Y_iaOfU%?0s3%>6^dotE;(!*z7x~FVLb;VodI6? z;*#zqFOoWedCHS<85owd?#-P5Mm?Lel3s&FKvEkpPjDIDOmzr~o~{cY6+A zm6sC)*kt9+Z=kmd4CRPp6X#1J(TX^xIaC4C8JdNn|J#I!9Wzk{dILCfBk>pFV85Wf z)GW}6NO6FU!rSm)po%LdLQt9qfrpik%_^|E7ve2Cv0BBIQ7wr}>Wnz7vanbIx^YqX zKL?Sd8t|z_@j=24tE@%vjsBCeQ7-{(ab#C1v=G_L%EY1EOfig$o>z=HR=CsI7Yft2_H za6a7|Wc3AGxbV%kAex6~xLviC9w_V$t*yFG<4R&s!zJ9yJcF{{@CDG1s~MLfgqLhi ze~~x@bjK-&27uGR30%X{VMCk1H{dhyKBhhY1Iw8zU3ZLc{Qv*}07*qoM6N<$g7dXZ Ac>n+a literal 0 HcmV?d00001 diff --git a/code/src/EnvironmentManager/Images/dump_32x32.png b/code/src/EnvironmentManager/Images/dump_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..fd1e159aa3961741fd580942bd1df6ced3e9d7e6 GIT binary patch literal 695 zcmV;o0!aOdP)U!q~+`I}3{lD!JH*LKFffg(8ZApi%IMTD&>3oVV|0pWX)#=FNUH z^WEFq+1)ltq(ag`U=Y}8uJ=Gi((}|@ZC(MAHUXo+MPN^a_6E2LOi5bz05^dP_LPCuV0-{rwAftaPJyH@vR}U6Twj1ozzs>& zI&LR$4!8{LF!xJfP|}ZBjgBS)$AaQ(z_6rBL~IqfF6kNY5ZG$5=YcD+0?{duv;`Os zTqCjKb#V*8*}ye!xzYpxCxLFWUjuiW6%R0DF_2Bii6R1y1$J4|Z*O(umLs{`0>Wjq z;w>IvHImCMU~^!9E+F7jB$r#jyTI-%AYgAKms`M6U>`3aU?{L(6%jBC)cSczQopyd zqys?7Uf0Q4L3w@P12AW=9l&JXdJf{c$o7L;lbef1CNah z@Re+@h$2^jq_<>Utu_^hfJfH%>$#o4ym7FYQOi+GfF+&*Mgmta8yzx4w~f_s#$t2f zJr7I+%O0*_S<)R3(Ivp}vlZNC+i?sfN%M8Sp(2Tj3YaVy_ZL#8pe0E80@G}Cw`RE4 z3IVr(1t&M!`~OA29B{zUZFJN5PPY5Gi$mfR1VZ|N(?D%ot^&6u dEybf6;}6Z9tRX?$B|HEC002ovPDHLkV1hB7E<^wT literal 0 HcmV?d00001 diff --git a/code/src/EnvironmentManager/Images/exit_32x32.png b/code/src/EnvironmentManager/Images/exit_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..b8117c850a3112d674d360031e74d24fc0fdca04 GIT binary patch literal 828 zcmV-C1H=4@P)U6lPewJ2THvP}jF zwW}5_bMXpUn---7(x#0YtxbiNP(k!VZL~!bWQ0VbhES9X`_XK1&e)kV=g!=5?1js4 zzw@2v!N7lM$*qhxt0{HZqNvva_;v4{lJ|x4!kqSE(FDAdv@VtbNy~vgVACRqr?DC6 zx7@i6U1CbV+(! zsJLSy1#|_LnnMN}yremxeWBtG+AWuheHjL9d)@9O4N00PR^6V5Auqf`QrWx#U^P(c zCA}$D+(F8_FSVNU212d$>mnKmy}p&Mfl!Cbs#--L^arP117l>rE?aXDev284;+nCV z^M`O281a&7C9RAg8~{3iNA~Klu`Ebh1=M)qk#vi8=Yat-8&ElL9Ow*?{sPWMnDfzW zx%@u>WY&SZfz9XbG_)Yty{&aAZ4{O4cKSW7T{{M zCSs59l9pQW&_L7Xj%a>2yan73Y&rrwla!1ho&=r(hXc#~z->2>@V|^Ct;O{64G|g- zfSbT`AYH20V?It!U`DVI#xNy#(hY`2vPs~OL++yq=mAE7-}V}{u_*Bg;Gi!aG+<4n zFx_!Df-sE)|EAz5311K85dbjlRHPX(Rq{jis-V z!cMTf###^yJ0C!Hxy8+$-f1zZ99 zozxmwO8OoY05}1zJ-IvJC8k(P3#!CiHh`=e0Y-okpg4f@CX_SN%y}%(jIVZI9mZX&GRK- zF@Ke`X$M?a@e^=pDAv}a@x3?Ts2y;%;2_dGM!nj_*Rc!WYe2tVZPyH(@w<+P(5f5g feFgum;xq6A{E1+qE7dqf00000NkvXXu0mjfX$73j literal 0 HcmV?d00001 diff --git a/code/src/EnvironmentManager/Images/modify_32x32.png b/code/src/EnvironmentManager/Images/modify_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..c4d1e8c8c1c1c8ba595f37ad172102b58814424e GIT binary patch literal 767 zcmV9j94{-6VX0D(`!Js5yKYW{ zWMCY#``FB>048xC9#QIK?^F)80Ftc4idvXO*JB$F;B1oJjRBI(R_?xu?!jff4C-WH z6t^}6_$bN##sEpKQwpky{>1uzM*T7{hItJEHYfSHF+h?#@Jz$tr?9z>VXq9l)n~q! z)+hO^F+h^l%J;pA_9gkGuJJ_xN#yliUE`U}BmL{3gK9OV$TRZH(Nb-4;;9UWdoW^gMk7G$zwas0Q{YrZU z7xaEKeKpn2<(SgzFT}1SU*QZ^fV^002ovPDHLkV1j+*WP<D~g`@_M z1n}rf`W&}elVr`Gfo1@J58znRgKyJ*fy86p1ulV~IgLBO1+Wd=NjmaD)C;fu>0Hw5 zG#5!HPUv3JyQD|p4A=o0;~-WhdY&z^Au(=38+pM7S0lWci zNokozl3KtMusa&}6=APza|4h9JvYAWzhD>}pr8t%0;mA~CBRxhzd~+bFX%`*1=5nu zTV%tE7obnp`1N=Fk17eNHat|moY(or29>lo-# zxBT7g63F7iQFk|LFg*jSqjED7DEUKrPSe_=*xZgzPuOB4-b-i&;9>D_uYHW zNkI~MA!!@f017sA0dytp=H)8nH6W=1d=E@lB@OfN6?3UaxVqq+7ngj4lFC3ESOuDr ze&i)Kp6fsdI0d#OP28Bo4w(1>un9Z@j*Ms#@gw8d1P+XQ1~X?MCVtO}>jQOQ0yKf0 zfNqQE7ne=^mNS10G~zqq?m*ikjuCwZhAHCM+|XiOag4hiA8^mWY6^MQ*HE@6gc%KY z2bx6Ny)5)i!*9S&dwoeJ`as>iQNCL+HIa7p&VYAB?|jETpQXePec}+v14O5NB`|jg zJasdkWyrU{x@XNb<4m9+X}UO`#h&0l?T5faN#{N?NhRPzz(fn!i+vwU8Wwk&S@3b5xEUA#pE7c86!}|H=izI{xID3iMXKR{MUkkP z$bvgLc(cY{6oGEW;0JcVQeiRoGjeu|)<#u<+p(wR2`&Mz?W%>uOTZiaH=yNcR3+_t zTQW&2MA^$%R2E4@6QCk#G=3rv6A5&#j|>@wVy$FNZR@&H=!1rU)|-~mvcM|2Jt$q^+7wgK3Zr*|GWAtH~n zi4s@ANe}ZMz*C?Ts0XTn1W*MethEViZI!h)F+M(Ctu+^ce(lYTa`pHZrLKbP_%m=@ zXZW7~H(deUv6?gK{0 zad*)Y01=r1I&>I{L?V|nnM@myDZ)GhwBjuPcFlE&NV*_^LfpaGk^vkF1Om+}IJXS* zIp8P|1rBO1AtHCZit{}O&Hyj;cZV^iC7n(;0-y5qd;}VSeZU3HJqOPEHTVLE$RFS^ z&fXB0fTdPs8!oVL~blsv#6Jmh+GG1fQP_h&ZR}o)f8h6MXrDgCXBNh;LQ5fN_74L XChFl~R-zsX00000NkvXXu0mjfBpptW literal 0 HcmV?d00001 diff --git a/code/src/EnvironmentManager/Internals/PermissionCheck.cs b/code/src/EnvironmentManager/Internals/PermissionCheck.cs new file mode 100644 index 0000000..b8e3d12 --- /dev/null +++ b/code/src/EnvironmentManager/Internals/PermissionCheck.cs @@ -0,0 +1,83 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Internals +{ + internal static class PermissionCheck + { + private static Boolean? isRunAsAdmin = null; + + internal static Boolean IsRunAsAdmin + { + get + { + // NOTE: This state will not change over the process lifetime. + + if (!PermissionCheck.isRunAsAdmin.HasValue) + { + PermissionCheck.isRunAsAdmin = PermissionCheck.IsUserAnAdmin(); + } + + return PermissionCheck.isRunAsAdmin.Value; + } + } + + internal static void SetButtonShield(Button button, Boolean visible) + { + const Int32 BCM_SETSHIELD = 0x0000160C; + + if (button != null) + { + // Important because otherwise shield is not shown! + if (button.FlatStyle != FlatStyle.System) + { + button.FlatStyle = FlatStyle.System; + } + + HandleRef hWnd = new HandleRef(button, button.Handle); + IntPtr lParam = visible ? new IntPtr(1) : new IntPtr(0); + + PermissionCheck.SendMessage(hWnd, BCM_SETSHIELD, IntPtr.Zero, lParam); + } + } + + #region Win32 related declaration and implementation section. + + // Windows 2000 Professional / Windows 2000 Server + // Remarks are taken from the MSDN: "This function is a wrapper for CheckTokenMembership. + // It is recommended to call that function directly to determine Administrator group status + // rather than calling IsUserAnAdmin." + [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall)] + private static extern Boolean IsUserAnAdmin(); + + [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr SendMessage(HandleRef hWnd, Int32 message, IntPtr wParam, IntPtr lParam); + + #endregion // Win32 related declaration and implementation section. + } +} diff --git a/code/src/EnvironmentManager/Internals/SelfElevation.cs b/code/src/EnvironmentManager/Internals/SelfElevation.cs new file mode 100644 index 0000000..860771f --- /dev/null +++ b/code/src/EnvironmentManager/Internals/SelfElevation.cs @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.LogWriter.Extensions; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Internals +{ + internal static class SelfElevation + { + public static Boolean Elevate(String parameters) + { + return Elevate(parameters, false); + } + + public static Boolean Elevate(String parameters, Boolean wait) + { + try + { + // Be aware, starting a process with different window styles does not really + // work! Therefore, starting the sibling process with administrator privileges + // uses appropriated command line arguments instead. + + ProcessStartInfo info = new ProcessStartInfo + { + Verb = "runas", + Arguments = parameters, + FileName = Application.ExecutablePath + }; + + if (wait) + { + Process process = Process.Start(info); + process.WaitForExit(); + + // By definition the self elevated program + // returns zero if execution was successful! + return process.ExitCode == 0; + } + else + { + Process.Start(info); + return true; + } + } + catch (Win32Exception exception) + { + const Int32 ERROR_CANCELLED = 1223; + + if (exception.NativeErrorCode == ERROR_CANCELLED) + { + Program.Logger.Warning("User rejected self-elevation.", new (String, Object)[] { ("Parameters", parameters), ("Wait", wait) }); + return false; + } + else + { + Program.Logger.Critical("Unexpected error while self-elevation.", exception); + throw exception; + } + } + } + } +} diff --git a/code/src/EnvironmentManager/Internals/TaskScheduler.cs b/code/src/EnvironmentManager/Internals/TaskScheduler.cs new file mode 100644 index 0000000..70d652f --- /dev/null +++ b/code/src/EnvironmentManager/Internals/TaskScheduler.cs @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.LogWriter.Extensions; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Internals +{ + internal static class TaskScheduler + { + public static Boolean IsInstalled() + { + return TaskScheduler.ExecuteCommand(TaskScheduler.GetQueryTaskCommandArguments()); + } + + public static Boolean Create() + { + return TaskScheduler.ExecuteCommand(TaskScheduler.GetCreateTaskCommandArguments(), !PermissionCheck.IsRunAsAdmin); + } + + public static Boolean Delete() + { + // Not really nice, because of UAC window is shown three times. + + Boolean result = false; + Boolean elevated = !PermissionCheck.IsRunAsAdmin; + + // Might be unsuccessful, e.g. is not running. + TaskScheduler.ExecuteCommand(TaskScheduler.GetFinishTaskCommandArguments(), elevated); + + result = TaskScheduler.ExecuteCommand(TaskScheduler.GetRemoveTaskCommandArguments(), elevated); + + // Might be unsuccessful, e.g. is not running. + TaskScheduler.ExecuteCommand(TaskScheduler.GetRemoveRootCommandArguments(), elevated); + + return result; + } + + private static Boolean ExecuteCommand(String arguments) + { + return TaskScheduler.ExecuteCommand(arguments, false); + } + + private static Boolean ExecuteCommand(String arguments, Boolean elevated) + { + try + { + ProcessStartInfo info = new ProcessStartInfo + { + Verb = elevated ? "runas" : String.Empty, + Arguments = arguments ?? String.Empty, + FileName = TaskScheduler.GetTaskSchedulerExecutable(), + WindowStyle = ProcessWindowStyle.Hidden + }; + + Debug.WriteLine(($"{info.Verb} {info.FileName} {info.Arguments}").Trim()); + + using (Process process = Process.Start(info)) + { + process.WaitForExit(); + return process.ExitCode == 0; + } + } + catch (Win32Exception exception) + { + const Int32 ERROR_CANCELLED = 1223; + + if (exception.NativeErrorCode == ERROR_CANCELLED) + { + Program.Logger.Warning("User rejected command execution in task scheduler.", new (String, Object)[] { ("Arguments", arguments), ("Elevated", elevated) }); + return false; + } + else + { + Program.Logger.Critical("Unexpected error while command execution in task scheduler.", exception); + throw exception; + } + } + } + + private static String GetTaskSchedulerExecutable() + { + return Environment.ExpandEnvironmentVariables( + $"\"%SystemRoot%\\System32\\schtasks.exe\"" + ); + } + + private static String GetTaskRootName() + { + return $"\\Plexdata"; + } + + private static String GetTaskFullName() + { + return $"{TaskScheduler.GetTaskRootName()}\\{Application.ProductName} (Tray Icon Runner)"; + } + + private static String GetTaskFullPath() + { + return $"'{Assembly.GetExecutingAssembly().Location}' --tray-icon"; + } + + public static String GetQueryTaskCommandArguments() + { + return Environment.ExpandEnvironmentVariables( + $"/QUERY " + + $"/TN \"{TaskScheduler.GetTaskFullName()}\"" + ); + } + + public static String GetCreateTaskCommandArguments() + { + return Environment.ExpandEnvironmentVariables( + $"/CREATE " + + $"/TN \"{TaskScheduler.GetTaskFullName()}\" " + + $"/TR \"{TaskScheduler.GetTaskFullPath()}\" " + + $"/SC ONLOGON " + + $"/RL HIGHEST " + + $"/RU %USERDOMAIN%\\%USERNAME%" + ); + } + + public static String GetFinishTaskCommandArguments() + { + return Environment.ExpandEnvironmentVariables( + $"/END " + + $"/TN \"{TaskScheduler.GetTaskFullName()}\"" + ); + } + + public static String GetRemoveRootCommandArguments() + { + return Environment.ExpandEnvironmentVariables( + $"/DELETE " + + $"/TN \"{TaskScheduler.GetTaskRootName()}\" " + + $"/F" + ); + } + + public static String GetRemoveTaskCommandArguments() + { + return Environment.ExpandEnvironmentVariables( + $"/DELETE " + + $"/TN \"{TaskScheduler.GetTaskFullName()}\" " + + $"/F" + ); + } + } +} diff --git a/code/src/EnvironmentManager/Internals/WaitCursor.cs b/code/src/EnvironmentManager/Internals/WaitCursor.cs new file mode 100644 index 0000000..348a2a0 --- /dev/null +++ b/code/src/EnvironmentManager/Internals/WaitCursor.cs @@ -0,0 +1,83 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager.Internals +{ + internal class WaitCursor : IDisposable + { + private WaitCursor() + : base() + { + this.Control = null; + this.Cursor = Cursors.Default; + } + + public WaitCursor(Control control) + : this(control, false) + { + } + + public WaitCursor(Control control, Boolean highest) + : this() + { + if (highest) + { + if (control != null) + { + Control parent = control.Parent; + + while (parent != null) + { + control = parent; + parent = control.Parent; + } + } + } + + this.Control = control; + + if (this.Control != null) + { + this.Cursor = this.Control.Cursor; + this.Control.Cursor = Cursors.WaitCursor; + } + } + + public Control Control { get; private set; } + + public Cursor Cursor { get; private set; } + + public void Dispose() + { + if (this.Control != null) + { + this.Control.Cursor = Cursor; + } + } + } + +} diff --git a/code/src/EnvironmentManager/MainForm.Designer.cs b/code/src/EnvironmentManager/MainForm.Designer.cs new file mode 100644 index 0000000..70d159f --- /dev/null +++ b/code/src/EnvironmentManager/MainForm.Designer.cs @@ -0,0 +1,449 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Plexdata.EnvironmentManager +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.tbsMain = new System.Windows.Forms.ToolStrip(); + this.tbbExit = new System.Windows.Forms.ToolStripButton(); + this.tbsSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.tbbSave = new System.Windows.Forms.ToolStripButton(); + this.tbsSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.tbbCreate = new System.Windows.Forms.ToolStripButton(); + this.tbbModify = new System.Windows.Forms.ToolStripButton(); + this.tbbDelete = new System.Windows.Forms.ToolStripButton(); + this.tbsSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.tbbElevate = new System.Windows.Forms.ToolStripButton(); + this.tbbDump = new System.Windows.Forms.ToolStripButton(); + this.tbbHide = new System.Windows.Forms.ToolStripButton(); + this.tbsSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.tbbSettings = new System.Windows.Forms.ToolStripButton(); + this.stbMain = new System.Windows.Forms.StatusStrip(); + this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.notifyMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.cmiExit = new System.Windows.Forms.ToolStripMenuItem(); + this.cmiShow = new System.Windows.Forms.ToolStripMenuItem(); + this.cmiSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.cmiHide = new System.Windows.Forms.ToolStripMenuItem(); + this.splContainer = new Plexdata.EnvironmentManager.Controls.SplitContainerEx(); + this.grpUser = new System.Windows.Forms.GroupBox(); + this.lsvUser = new System.Windows.Forms.ListView(); + this.grpMachine = new System.Windows.Forms.GroupBox(); + this.lsvMachine = new System.Windows.Forms.ListView(); + this.tbsMain.SuspendLayout(); + this.notifyMenu.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splContainer)).BeginInit(); + this.splContainer.Panel1.SuspendLayout(); + this.splContainer.Panel2.SuspendLayout(); + this.splContainer.SuspendLayout(); + this.grpUser.SuspendLayout(); + this.grpMachine.SuspendLayout(); + this.SuspendLayout(); + // + // tbsMain + // + this.tbsMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.tbbExit, + this.tbsSeparator1, + this.tbbSave, + this.tbsSeparator2, + this.tbbCreate, + this.tbbModify, + this.tbbDelete, + this.tbsSeparator3, + this.tbbElevate, + this.tbbDump, + this.tbbHide, + this.tbsSeparator4, + this.tbbSettings}); + this.tbsMain.Location = new System.Drawing.Point(0, 0); + this.tbsMain.Name = "tbsMain"; + this.tbsMain.Size = new System.Drawing.Size(660, 47); + this.tbsMain.TabIndex = 2; + this.tbsMain.Text = "toolStrip1"; + // + // tbbExit + // + this.tbbExit.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbExit.Image = global::Plexdata.EnvironmentManager.Properties.Resources.ExitLargeIcon; + this.tbbExit.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbExit.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbExit.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbExit.Name = "tbbExit"; + this.tbbExit.Padding = new System.Windows.Forms.Padding(3); + this.tbbExit.Size = new System.Drawing.Size(42, 42); + this.tbbExit.Text = "Exit"; + this.tbbExit.ToolTipText = "Close main window and exit application."; + this.tbbExit.Click += new System.EventHandler(this.OnExitClicked); + // + // tbsSeparator1 + // + this.tbsSeparator1.Name = "tbsSeparator1"; + this.tbsSeparator1.Size = new System.Drawing.Size(6, 47); + // + // tbbSave + // + this.tbbSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbSave.Image = global::Plexdata.EnvironmentManager.Properties.Resources.SaveLargeIcon; + this.tbbSave.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbSave.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbSave.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbSave.Name = "tbbSave"; + this.tbbSave.Padding = new System.Windows.Forms.Padding(3); + this.tbbSave.Size = new System.Drawing.Size(42, 42); + this.tbbSave.Text = "Save"; + this.tbbSave.ToolTipText = "Write all changes back to the system."; + this.tbbSave.Click += new System.EventHandler(this.OnSaveClicked); + // + // tbsSeparator2 + // + this.tbsSeparator2.Name = "tbsSeparator2"; + this.tbsSeparator2.Size = new System.Drawing.Size(6, 47); + // + // tbbCreate + // + this.tbbCreate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbCreate.Image = global::Plexdata.EnvironmentManager.Properties.Resources.CreateLargeIcon; + this.tbbCreate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbCreate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbCreate.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbCreate.Name = "tbbCreate"; + this.tbbCreate.Padding = new System.Windows.Forms.Padding(3); + this.tbbCreate.Size = new System.Drawing.Size(42, 42); + this.tbbCreate.Text = "Create"; + this.tbbCreate.ToolTipText = "Create new environment variable."; + this.tbbCreate.Click += new System.EventHandler(this.OnCreateClicked); + // + // tbbModify + // + this.tbbModify.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbModify.Image = global::Plexdata.EnvironmentManager.Properties.Resources.ModifyLargeIcon; + this.tbbModify.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbModify.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbModify.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbModify.Name = "tbbModify"; + this.tbbModify.Padding = new System.Windows.Forms.Padding(3); + this.tbbModify.Size = new System.Drawing.Size(42, 42); + this.tbbModify.Text = "Modify"; + this.tbbModify.ToolTipText = "Modify selected environment variable."; + this.tbbModify.Click += new System.EventHandler(this.OnModifyClicked); + // + // tbbDelete + // + this.tbbDelete.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbDelete.Image = global::Plexdata.EnvironmentManager.Properties.Resources.DeleteLargeIcon; + this.tbbDelete.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbDelete.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbDelete.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbDelete.Name = "tbbDelete"; + this.tbbDelete.Padding = new System.Windows.Forms.Padding(3); + this.tbbDelete.Size = new System.Drawing.Size(42, 42); + this.tbbDelete.Text = "Delete"; + this.tbbDelete.ToolTipText = "Indicate selected environment variable as deleted."; + this.tbbDelete.Click += new System.EventHandler(this.OnDeleteClicked); + // + // tbsSeparator3 + // + this.tbsSeparator3.Name = "tbsSeparator3"; + this.tbsSeparator3.Size = new System.Drawing.Size(6, 47); + // + // tbbElevate + // + this.tbbElevate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbElevate.Image = global::Plexdata.EnvironmentManager.Properties.Resources.ShieldLargeIcon; + this.tbbElevate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbElevate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbElevate.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbElevate.Name = "tbbElevate"; + this.tbbElevate.Padding = new System.Windows.Forms.Padding(3); + this.tbbElevate.Size = new System.Drawing.Size(42, 42); + this.tbbElevate.Text = "Elevate"; + this.tbbElevate.ToolTipText = "Restart application in administrator mode."; + this.tbbElevate.Click += new System.EventHandler(this.OnElevateClicked); + // + // tbbDump + // + this.tbbDump.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbDump.Image = global::Plexdata.EnvironmentManager.Properties.Resources.DumpLargeIcon; + this.tbbDump.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbDump.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbDump.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbDump.Name = "tbbDump"; + this.tbbDump.Padding = new System.Windows.Forms.Padding(3); + this.tbbDump.Size = new System.Drawing.Size(42, 42); + this.tbbDump.Text = "Dump"; + this.tbbDump.ToolTipText = "Dump all environment variables."; + this.tbbDump.Click += new System.EventHandler(this.OnDumpClicked); + // + // tbbHide + // + this.tbbHide.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbHide.Image = global::Plexdata.EnvironmentManager.Properties.Resources.FollowLargeIcon; + this.tbbHide.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbHide.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbHide.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbHide.Name = "tbbHide"; + this.tbbHide.Padding = new System.Windows.Forms.Padding(3); + this.tbbHide.Size = new System.Drawing.Size(42, 42); + this.tbbHide.Text = "Hide"; + this.tbbHide.ToolTipText = "Hide user interface and run in tray icon mode."; + this.tbbHide.Click += new System.EventHandler(this.OnHideClicked); + // + // tbsSeparator4 + // + this.tbsSeparator4.Name = "tbsSeparator4"; + this.tbsSeparator4.Size = new System.Drawing.Size(6, 47); + // + // tbbSettings + // + this.tbbSettings.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tbbSettings.Image = global::Plexdata.EnvironmentManager.Properties.Resources.SettingsLargeIcon; + this.tbbSettings.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.tbbSettings.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tbbSettings.Margin = new System.Windows.Forms.Padding(2, 2, 2, 3); + this.tbbSettings.Name = "tbbSettings"; + this.tbbSettings.Padding = new System.Windows.Forms.Padding(3); + this.tbbSettings.Size = new System.Drawing.Size(42, 42); + this.tbbSettings.Text = "Settings"; + this.tbbSettings.ToolTipText = "Modify application settings."; + this.tbbSettings.Click += new System.EventHandler(this.OnSettingsClicked); + // + // stbMain + // + this.stbMain.Location = new System.Drawing.Point(0, 532); + this.stbMain.Name = "stbMain"; + this.stbMain.Size = new System.Drawing.Size(660, 22); + this.stbMain.TabIndex = 3; + this.stbMain.Text = "statusStrip1"; + // + // notifyIcon + // + this.notifyIcon.ContextMenuStrip = this.notifyMenu; + this.notifyIcon.Text = "Environment Manager"; + this.notifyIcon.Visible = true; + this.notifyIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.OnNotifyDoubleClicked); + // + // notifyMenu + // + this.notifyMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.cmiExit, + this.cmiShow, + this.cmiSeparator1, + this.cmiHide}); + this.notifyMenu.Name = "notifyMenu"; + this.notifyMenu.Size = new System.Drawing.Size(104, 76); + this.notifyMenu.Opening += new System.ComponentModel.CancelEventHandler(this.OnNotifyMenuOpening); + // + // cmiExit + // + this.cmiExit.Name = "cmiExit"; + this.cmiExit.Size = new System.Drawing.Size(103, 22); + this.cmiExit.Text = "&Exit"; + this.cmiExit.Click += new System.EventHandler(this.OnNotifyExitClicked); + // + // cmiShow + // + this.cmiShow.Name = "cmiShow"; + this.cmiShow.Size = new System.Drawing.Size(103, 22); + this.cmiShow.Text = "&Show"; + this.cmiShow.Click += new System.EventHandler(this.OnNotifyShowClicked); + // + // cmiSeparator1 + // + this.cmiSeparator1.Name = "cmiSeparator1"; + this.cmiSeparator1.Size = new System.Drawing.Size(100, 6); + // + // cmiHide + // + this.cmiHide.Name = "cmiHide"; + this.cmiHide.Size = new System.Drawing.Size(103, 22); + this.cmiHide.Text = "&Hide"; + this.cmiHide.Click += new System.EventHandler(this.OnNotifyHideClicked); + // + // splContainer + // + this.splContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.splContainer.Location = new System.Drawing.Point(0, 47); + this.splContainer.Name = "splContainer"; + this.splContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splContainer.Panel1 + // + this.splContainer.Panel1.Controls.Add(this.grpUser); + this.splContainer.Panel1.Padding = new System.Windows.Forms.Padding(0, 10, 0, 5); + this.splContainer.Panel1MinSize = 150; + // + // splContainer.Panel2 + // + this.splContainer.Panel2.Controls.Add(this.grpMachine); + this.splContainer.Panel2MinSize = 150; + this.splContainer.Size = new System.Drawing.Size(660, 485); + this.splContainer.SplitterDistance = 178; + this.splContainer.SplitterWidth = 10; + this.splContainer.TabIndex = 0; + this.splContainer.TabStop = false; + // + // grpUser + // + this.grpUser.Controls.Add(this.lsvUser); + this.grpUser.Dock = System.Windows.Forms.DockStyle.Fill; + this.grpUser.Location = new System.Drawing.Point(0, 10); + this.grpUser.Name = "grpUser"; + this.grpUser.Padding = new System.Windows.Forms.Padding(10); + this.grpUser.Size = new System.Drawing.Size(660, 163); + this.grpUser.TabIndex = 0; + this.grpUser.TabStop = false; + this.grpUser.Text = "User Scope"; + // + // lsvUser + // + this.lsvUser.Dock = System.Windows.Forms.DockStyle.Fill; + this.lsvUser.FullRowSelect = true; + this.lsvUser.LabelWrap = false; + this.lsvUser.Location = new System.Drawing.Point(10, 23); + this.lsvUser.MultiSelect = false; + this.lsvUser.Name = "lsvUser"; + this.lsvUser.ShowGroups = false; + this.lsvUser.ShowItemToolTips = true; + this.lsvUser.Size = new System.Drawing.Size(640, 130); + this.lsvUser.Sorting = System.Windows.Forms.SortOrder.Ascending; + this.lsvUser.TabIndex = 0; + this.lsvUser.UseCompatibleStateImageBehavior = false; + this.lsvUser.View = System.Windows.Forms.View.Details; + this.lsvUser.Click += new System.EventHandler(this.OnListViewEnter); + this.lsvUser.Enter += new System.EventHandler(this.OnListViewEnter); + // + // grpMachine + // + this.grpMachine.Controls.Add(this.lsvMachine); + this.grpMachine.Dock = System.Windows.Forms.DockStyle.Fill; + this.grpMachine.Location = new System.Drawing.Point(0, 0); + this.grpMachine.Name = "grpMachine"; + this.grpMachine.Padding = new System.Windows.Forms.Padding(10); + this.grpMachine.Size = new System.Drawing.Size(660, 297); + this.grpMachine.TabIndex = 0; + this.grpMachine.TabStop = false; + this.grpMachine.Text = "Machine Scope"; + // + // lsvMachine + // + this.lsvMachine.Dock = System.Windows.Forms.DockStyle.Fill; + this.lsvMachine.FullRowSelect = true; + this.lsvMachine.LabelWrap = false; + this.lsvMachine.Location = new System.Drawing.Point(10, 23); + this.lsvMachine.MultiSelect = false; + this.lsvMachine.Name = "lsvMachine"; + this.lsvMachine.ShowGroups = false; + this.lsvMachine.ShowItemToolTips = true; + this.lsvMachine.Size = new System.Drawing.Size(640, 264); + this.lsvMachine.Sorting = System.Windows.Forms.SortOrder.Ascending; + this.lsvMachine.TabIndex = 0; + this.lsvMachine.UseCompatibleStateImageBehavior = false; + this.lsvMachine.View = System.Windows.Forms.View.Details; + this.lsvMachine.Click += new System.EventHandler(this.OnListViewEnter); + this.lsvMachine.Enter += new System.EventHandler(this.OnListViewEnter); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(660, 554); + this.Controls.Add(this.splContainer); + this.Controls.Add(this.tbsMain); + this.Controls.Add(this.stbMain); + this.Name = "MainForm"; + this.Text = "Environment Manager"; + this.tbsMain.ResumeLayout(false); + this.tbsMain.PerformLayout(); + this.notifyMenu.ResumeLayout(false); + this.splContainer.Panel1.ResumeLayout(false); + this.splContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splContainer)).EndInit(); + this.splContainer.ResumeLayout(false); + this.grpUser.ResumeLayout(false); + this.grpMachine.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ListView lsvMachine; + private System.Windows.Forms.ListView lsvUser; + private System.Windows.Forms.GroupBox grpUser; + private System.Windows.Forms.GroupBox grpMachine; + private Controls.SplitContainerEx splContainer; + private System.Windows.Forms.ToolStrip tbsMain; + private System.Windows.Forms.ToolStripButton tbbExit; + private System.Windows.Forms.ToolStripSeparator tbsSeparator1; + private System.Windows.Forms.ToolStripButton tbbSave; + private System.Windows.Forms.ToolStripSeparator tbsSeparator2; + private System.Windows.Forms.ToolStripButton tbbCreate; + private System.Windows.Forms.ToolStripButton tbbModify; + private System.Windows.Forms.ToolStripButton tbbDelete; + private System.Windows.Forms.ToolStripButton tbbElevate; + private System.Windows.Forms.ToolStripButton tbbDump; + private System.Windows.Forms.StatusStrip stbMain; + private System.Windows.Forms.ToolStripSeparator tbsSeparator4; + private System.Windows.Forms.NotifyIcon notifyIcon; + private System.Windows.Forms.ContextMenuStrip notifyMenu; + private System.Windows.Forms.ToolStripMenuItem cmiExit; + private System.Windows.Forms.ToolStripMenuItem cmiShow; + private System.Windows.Forms.ToolStripSeparator cmiSeparator1; + private System.Windows.Forms.ToolStripMenuItem cmiHide; + private System.Windows.Forms.ToolStripButton tbbHide; + private System.Windows.Forms.ToolStripSeparator tbsSeparator3; + private System.Windows.Forms.ToolStripButton tbbSettings; + } +} + diff --git a/code/src/EnvironmentManager/MainForm.cs b/code/src/EnvironmentManager/MainForm.cs new file mode 100644 index 0000000..6c20f6f --- /dev/null +++ b/code/src/EnvironmentManager/MainForm.cs @@ -0,0 +1,716 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.EnvironmentManager.Dialogs; +using Plexdata.EnvironmentManager.Extensions; +using Plexdata.EnvironmentManager.Internals; +using Plexdata.EnvironmentManager.Models; +using Plexdata.EnvironmentManager.Serializers; +using Plexdata.LogWriter.Extensions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager +{ + // TODO: Consider sorting by column, see below how: + // https://support.microsoft.com/de-de/help/319401/how-to-sort-a-listview-control-by-a-column-in-visual-c + + public partial class MainForm : Form + { + private ListView activeListView = null; + private Font defaultFont = new Font("Consolas", 11.25f); + + public MainForm() + { + this.InitializeComponent(); + + this.notifyIcon.Visible = Program.Arguments.IsMinimized; + + this.SetDoubleBuffered(this.lsvUser); + this.lsvUser.DoubleClick += this.OnListViewDoubleClick; + this.lsvUser.KeyDown += this.OnListViewKeyDown; + + this.SetDoubleBuffered(this.lsvMachine); + this.lsvMachine.DoubleClick += this.OnListViewDoubleClick; + this.lsvMachine.KeyDown += this.OnListViewKeyDown; + + this.Icon = Properties.Resources.MainIcon; + this.notifyIcon.Icon = Properties.Resources.MainIcon; + + if (PermissionCheck.IsRunAsAdmin) + { + this.Text += " (ADMIN)"; + this.notifyIcon.Text += " (ADMIN)"; + } +#if !DEBUG + this.tbbDump.Visible = false; +#endif + } + + #region Overwritten protected methods + + protected override void OnLoad(EventArgs args) + { + base.OnLoad(args); + this.PerformReload(); + } + + protected override void OnShown(EventArgs args) + { + this.LoadSettings(); + base.OnShown(args); + } + + protected override void SetVisibleCore(Boolean value) + { + base.SetVisibleCore(this.notifyIcon.Visible ? false : value); + } + + protected override void OnClosing(CancelEventArgs args) + { + base.OnClosing(args); + this.SaveSettings(); + } + + #endregion + + #region List view events + + private void OnListViewKeyDown(Object sender, KeyEventArgs args) + { + switch (args.KeyCode) + { + case Keys.Insert: + args.Handled = true; + this.HandleCreate(); + break; + case Keys.F2: + case Keys.Enter: + args.Handled = true; + this.HandleModify(); + break; + case Keys.Delete: + args.Handled = true; + this.HandleDelete(); + break; + } + } + + private void OnListViewDoubleClick(Object sender, EventArgs args) + { + this.HandleModify(); + } + + private void OnListViewEnter(Object sender, EventArgs args) + { + this.activeListView = sender as ListView; + this.EnableButtons(); + } + + #endregion + + #region Tool bar events + + private void OnCreateClicked(Object sender, EventArgs args) + { + this.HandleCreate(); + } + + private void OnDeleteClicked(Object sender, EventArgs args) + { + this.HandleDelete(); + } + + private void OnModifyClicked(Object sender, EventArgs args) + { + this.HandleModify(); + } + + private void OnElevateClicked(Object sender, EventArgs args) + { + String message = "Do you want to start program in administration mode?"; + + if (this.CheckModified()) + { + message += String.Format("{0}{0}Be aware, all made changes are lost!", Environment.NewLine); + } + + if (DialogResult.Yes == MessageBox.Show(this, message, this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)) + { + if (SelfElevation.Elevate(String.Empty)) + { + this.Close(); + Application.Exit(); + } + } + } + + private void OnDumpClicked(Object sender, EventArgs args) + { + using (new WaitCursor(this)) + { + foreach (ListViewItem item in this.lsvUser.Items) + { + Program.Logger.Trace(((EnvironmentVariable)item.Tag).ToString()); + } + + foreach (ListViewItem item in this.lsvMachine.Items) + { + Program.Logger.Trace(((EnvironmentVariable)item.Tag).ToString()); + } + } + } + + private void OnSaveClicked(Object sender, EventArgs args) + { + String message = "Possibly dangerous operation! Do you really want to apply the environment changes you made?"; + + if (DialogResult.Yes == MessageBox.Show(this, message, this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2)) + { + IList exceptions = new List(); + + using (new WaitCursor(this)) + { + if (!EnvironmentSerializer.Save(this.GetModifiedVariables(this.lsvUser), ref exceptions)) + { + Program.Logger.Fatal("Error occurred while serializing user scope environment."); + } + + this.SetupListView(this.lsvUser, EnvironmentSerializer.Load(EnvironmentVariableTarget.User)); + + if (PermissionCheck.IsRunAsAdmin) + { + if (!EnvironmentSerializer.Save(this.GetModifiedVariables(this.lsvMachine), ref exceptions)) + { + Program.Logger.Fatal("Error occurred while serializing machine scope environment."); + } + + this.SetupListView(this.lsvMachine, EnvironmentSerializer.Load(EnvironmentVariableTarget.Machine)); + } + + foreach (Exception exception in exceptions) + { + Program.Logger.Error(exception); + } + + Program.SaveSettings(); + } + } + } + + private void OnExitClicked(Object sender, EventArgs args) + { + this.Close(); + Application.Exit(); + } + + private void OnHideClicked(Object sender, EventArgs args) + { + this.notifyIcon.Visible = true; + this.Hide(); + } + + private void OnSettingsClicked(Object sender, EventArgs args) + { + SettingsDialog dialog = new SettingsDialog(); + + dialog.ShowDialog(this); + + if (dialog.DialogResult == DialogResult.OK) + { + // TODO: Something useful later on... + } + } + + #endregion + + #region Notify icon events + + private void OnNotifyDoubleClicked(Object sender, MouseEventArgs args) + { + this.OnNotifyShowClicked(sender, args); + } + + private void OnNotifyMenuOpening(Object sender, CancelEventArgs args) + { + Point point = Control.MousePosition; + + using (new WaitCursor(this)) + { + this.notifyMenu.Items.Clear(); + + if (!this.Visible) + { + IEnumerable variables = + EnvironmentSerializer.Load(EnvironmentVariableTarget.User) + .Concat(EnvironmentSerializer.Load(EnvironmentVariableTarget.Machine)) + .Where(x => x.Shift != null && x.Shift.Length > 0); + + foreach (EnvironmentVariable variable in variables) + { + ToolStripMenuItem strip = new ToolStripMenuItem(variable.Label) + { + Tag = variable.Scope, + }; + + foreach (String shift in variable.Shift) + { + ToolStripMenuItem child = new ToolStripMenuItem(shift) + { + Checked = String.Compare(shift, variable.Value) == 0 + }; + + child.Click += this.OnNotifyItemClicked; + strip.DropDownItems.Add(child); + } + + this.notifyMenu.Items.Add(strip); + } + + if (variables.Any()) + { + this.notifyMenu.Items.Add(this.cmiSeparator1); + } + + this.notifyMenu.Items.Add(this.cmiShow); + } + else + { + this.notifyMenu.Items.Add(this.cmiHide); + } + + this.notifyMenu.Items.Add(this.cmiExit); + } + + this.notifyMenu.Show(point, ToolStripDropDownDirection.AboveLeft); + } + + private void OnNotifyItemClicked(Object sender, EventArgs args) + { + if (!(sender is ToolStripMenuItem affected) || affected.OwnerItem == null) + { + return; + } + + if (!Enum.IsDefined(typeof(EnvironmentVariableTarget), affected.OwnerItem.Tag)) + { + return; + } + + EnvironmentVariableTarget target = (EnvironmentVariableTarget)affected.OwnerItem.Tag; + String label = affected.OwnerItem.Text; + String value = affected.Text; + + try + { + if (target == EnvironmentVariableTarget.Machine && !PermissionCheck.IsRunAsAdmin) + { + String message = "Administrator privileges required to change this environment variable!"; + MessageBox.Show(message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + Environment.SetEnvironmentVariable(label, value, target); + } + catch (Exception exception) + { + + String message = "Could not change environment variable."; + Program.Logger.Error(message, exception, new (String, Object)[] { ("Label", label), ("Value", value), ("Target", target.ToString()) }); + MessageBox.Show($"{message}{Environment.NewLine}{Environment.NewLine}{exception.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void OnNotifyShowClicked(Object sender, EventArgs args) + { + this.notifyIcon.Visible = false; + this.PerformReload(); + this.Show(); + } + + private void OnNotifyHideClicked(Object sender, EventArgs args) + { + this.OnHideClicked(sender, args); + } + + private void OnNotifyExitClicked(Object sender, EventArgs args) + { + this.OnExitClicked(sender, args); + } + + #endregion + + #region Other private methods + + private void PerformReload() + { + using (new WaitCursor(this)) + { + this.SetupListView(this.lsvUser, EnvironmentSerializer.Load(EnvironmentVariableTarget.User)); + this.SetupListView(this.lsvMachine, EnvironmentSerializer.Load(EnvironmentVariableTarget.Machine)); + + if (PermissionCheck.IsRunAsAdmin) + { + this.tbbElevate.Visible = false; + this.tbsSeparator4.Visible = false; + } + + this.tbbSave.Enabled = this.CheckModified(); + + if (this.lsvUser.Items.Count > 0) + { + this.activeListView = this.lsvUser; + } + else if (this.lsvMachine.Items.Count > 0) + { + this.activeListView = this.lsvMachine; + } + + if (this.activeListView != null && this.activeListView.Items.Count > 0) + { + this.activeListView.Items[0].Selected = true; + } + + this.EnableButtons(); + } + } + + private ListViewItem CreateEntry(EnvironmentVariable variable) + { + return new ListViewItem(new String[] { variable.Label, variable.Value }) + { + Tag = variable, + ToolTipText = variable.GetTooltip(), + Font = this.GetItemFont(variable), + ForeColor = this.GetForeColor(variable), + BackColor = this.GetBackColor(variable) + }; + } + + private void SetupListView(ListView affected, IEnumerable variables) + { + affected.Clear(); + affected.Columns.Clear(); + + this.EnsureColumns(affected); + + foreach (EnvironmentVariable current in variables) + { + affected.Items.Add(this.CreateEntry(current)); + } + + this.ResizeColumns(affected); + } + + private void AppendListView(ListView affected, EnvironmentVariable variable) + { + this.EnsureColumns(affected); + + ListViewItem current = this.CreateEntry(variable); + + affected.Items.Add(current); + + current.Selected = true; + current.EnsureVisible(); + + this.ResizeColumns(affected); + } + + private void UpdateListView(ListViewItem affected, EnvironmentVariable variable) + { + affected.SubItems[0].Text = variable.Label; + affected.SubItems[1].Text = variable.Value; + affected.Tag = variable; + affected.ToolTipText = variable.GetTooltip(); + affected.Font = this.GetItemFont(variable); + affected.ForeColor = this.GetForeColor(variable); + affected.BackColor = this.GetBackColor(variable); + } + + private void HandleCreate() + { + if (this.activeListView == null) + { + return; + } + + if (this.activeListView == this.lsvMachine && !PermissionCheck.IsRunAsAdmin) + { + return; + } + + EnvironmentVariableTarget scope = this.activeListView == this.lsvMachine ? EnvironmentVariableTarget.Machine : EnvironmentVariableTarget.User; + + VariableDialog dialog = new VariableDialog(scope); + + dialog.ShowDialog(this); + + if (dialog.DialogResult == DialogResult.OK) + { + this.AppendListView(this.activeListView, dialog.Variable); + } + + this.tbbSave.Enabled = this.CheckModified(); + } + + private void HandleModify() + { + if (this.activeListView == null) + { + return; + } + + if (this.activeListView == this.lsvMachine && !PermissionCheck.IsRunAsAdmin) + { + return; + } + + if (this.activeListView.SelectedItems == null || this.activeListView.SelectedItems.Count < 1) + { + return; + } + + if (this.activeListView.SelectedItems[0].Tag is EnvironmentVariable current) + { + VariableDialog dialog = new VariableDialog(current); + + dialog.ShowDialog(this); + + if (dialog.DialogResult == DialogResult.OK) + { + this.UpdateListView(this.activeListView.SelectedItems[0], dialog.Variable); + } + } + + this.tbbSave.Enabled = this.CheckModified(); + } + + private void HandleDelete() + { + if (this.activeListView == null) + { + return; + } + + if (this.activeListView == this.lsvMachine && !PermissionCheck.IsRunAsAdmin) + { + return; + } + + if (this.activeListView.SelectedItems == null || this.activeListView.SelectedItems.Count < 1) + { + return; + } + + ListViewItem selected = this.activeListView.SelectedItems[0]; + + if (selected.Tag is EnvironmentVariable current) + { + if (current.IsCreated) + { + selected.Remove(); + } + else + { + current.TryChangeStage(EnvironmentVariable.Stages.Deleted); + + selected.ToolTipText = current.GetTooltip(); + selected.Font = this.GetItemFont(current); + selected.ForeColor = this.GetForeColor(current); + selected.BackColor = this.GetBackColor(current); + } + } + + this.tbbSave.Enabled = this.CheckModified(); + } + + private Color GetForeColor(EnvironmentVariable variable) + { + switch (variable.Stage) + { + case EnvironmentVariable.Stages.Created: + return Color.Black; + case EnvironmentVariable.Stages.Changed: + return Color.Black; + case EnvironmentVariable.Stages.Deleted: + return Color.WhiteSmoke; + case EnvironmentVariable.Stages.Nothing: + default: + return Color.Black; + } + } + + private Color GetBackColor(EnvironmentVariable variable) + { + switch (variable.Stage) + { + case EnvironmentVariable.Stages.Created: + return Color.Lime; + case EnvironmentVariable.Stages.Changed: + return Color.Khaki; + case EnvironmentVariable.Stages.Deleted: + return Color.Crimson; + case EnvironmentVariable.Stages.Nothing: + default: + return Color.White; + } + } + + private Font GetItemFont(EnvironmentVariable variable) + { + switch (variable.Stage) + { + case EnvironmentVariable.Stages.Created: + case EnvironmentVariable.Stages.Changed: + case EnvironmentVariable.Stages.Deleted: + case EnvironmentVariable.Stages.Nothing: + default: + return this.defaultFont; + } + } + + private void EnableButtons() + { + if (this.activeListView == this.lsvMachine) + { + Boolean enabled = this.CheckSelected(this.lsvMachine) && PermissionCheck.IsRunAsAdmin; + + this.tbbCreate.Enabled = PermissionCheck.IsRunAsAdmin; + this.tbbModify.Enabled = enabled; + this.tbbDelete.Enabled = enabled; + } + else + { + Boolean enabled = this.CheckSelected(this.lsvUser); + + this.tbbCreate.Enabled = true; + this.tbbModify.Enabled = enabled; + this.tbbDelete.Enabled = enabled; + } + } + + private void EnsureColumns(ListView affected) + { + if (affected.Columns.Count < 1) + { + affected.Columns.AddRange( + new ColumnHeader[] + { + new ColumnHeader() { Text = nameof(EnvironmentVariable.Label) }, + new ColumnHeader() { Text = nameof(EnvironmentVariable.Value) }, + }); + } + } + + private void ResizeColumns(ListView affected) + { + foreach (ColumnHeader column in affected.Columns) + { + column.Width = -2; + } + } + + private Boolean CheckSelected(ListView affected) + { + return affected.Items.Count > 0 && affected.Items + .Cast() + .Any(x => x.Selected); + } + + private Boolean CheckModified() + { + return this.CountModified(this.lsvUser) > 0 || this.CountModified(this.lsvMachine) > 0; + } + + private Int32 CountModified(ListView affected) + { + return affected.Items + .Cast() + .Select(x => x.Tag as EnvironmentVariable) + .Where(x => x.IsModified) + .Count(); + } + + private IEnumerable GetModifiedVariables(ListView affected) + { + return affected.Items + .Cast() + .Where(x => (x.Tag as EnvironmentVariable).IsModified) + .Select(x => (x.Tag as EnvironmentVariable)) + .AsEnumerable(); + } + + private void SetDoubleBuffered(Control control) + { + typeof(Control).InvokeMember("DoubleBuffered", + BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, + null, control, new Object[] { true }); + } + + private void LoadSettings() + { + try + { + // Keep this order of settings assignments! + + this.DesktopBounds = Program.ReadSettingsValue(this.GetType().Name, nameof(this.DesktopBounds)) + .StringToBounds(this.StandardBounds(new Size(700, 600))); + + Int32 minimumDistance = this.splContainer.Panel1.Top + this.splContainer.Panel1MinSize; + Int32 maximumDistance = this.splContainer.Panel2.Bottom - this.splContainer.Panel2MinSize; + Int32 defaultDistance = this.splContainer.SplitterDistance; + + this.splContainer.SplitterDistance = Program.ReadSettingsValue(this.GetType().Name, nameof(this.splContainer.SplitterDistance)) + .StringToInteger(minimumDistance, maximumDistance, defaultDistance); + } + catch (Exception exception) + { + Program.Logger.Error("Loading settings for main window from configuration has failed.", exception); + } + + this.EnsureScreenLocation(); + } + + private void SaveSettings() + { + try + { + Program.SaveSettingsValue(this.GetType().Name, nameof(this.splContainer.SplitterDistance), this.splContainer.SplitterDistance.IntegerToString()); + Program.SaveSettingsValue(this.GetType().Name, nameof(this.DesktopBounds), this.DesktopBounds.BoundsToString()); + Program.SaveSettings(); + } + catch (Exception exception) + { + Program.Logger.Error("Saving settings for main window into configuration has failed.", exception); + } + } + + #endregion + } +} diff --git a/code/src/EnvironmentManager/MainForm.resx b/code/src/EnvironmentManager/MainForm.resx new file mode 100644 index 0000000..15899a0 --- /dev/null +++ b/code/src/EnvironmentManager/MainForm.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 113, 17 + + + 209, 17 + + + 315, 17 + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/Models/CommandArguments.cs b/code/src/EnvironmentManager/Models/CommandArguments.cs new file mode 100644 index 0000000..c9d2bb2 --- /dev/null +++ b/code/src/EnvironmentManager/Models/CommandArguments.cs @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.ArgumentParser.Attributes; +using System; + +namespace Plexdata.EnvironmentManager.Models +{ + [HelpLicense] + [HelpUtilize] + [HelpPreface] + [ParametersGroup] + public class CommandArguments + { + [SwitchParameter(BriefLabel = "t", SolidLabel = "tray-icon", IsExclusive = true)] + [HelpSummary("Use this argument to initially start the program minimized and as tray icon.")] + public Boolean IsMinimized { get; set; } + + [SwitchParameter(BriefLabel = "i", SolidLabel = "install", IsExclusive = true)] + [HelpSummary("Use this option to register the program for auto-launch on user login. This option requires a program startup with administrator privileges.")] + public Boolean IsInstall { get; set; } + + [SwitchParameter(BriefLabel = "r", SolidLabel = "remove", IsExclusive = true)] + [HelpSummary("Use this option to remove the program registration for auto-launch on user login. This option requires a program startup with administrator privileges.")] + public Boolean IsRemove { get; set; } + + [SwitchParameter(BriefLabel = "?", SolidLabel = "help")] + [HelpSummary("Show this arguments help screen.")] + public Boolean IsHelp { get; set; } + } +} diff --git a/code/src/EnvironmentManager/Models/EnvironmentVariable.cs b/code/src/EnvironmentManager/Models/EnvironmentVariable.cs new file mode 100644 index 0000000..804082a --- /dev/null +++ b/code/src/EnvironmentManager/Models/EnvironmentVariable.cs @@ -0,0 +1,432 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.CfgParser.Entities; +using Plexdata.EnvironmentManager.Internals; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Plexdata.EnvironmentManager.Models +{ + public class EnvironmentVariable : ICloneable + { + private static readonly String ConfigSectionName = "EnvironmentVariables"; + private static readonly String LineSeparator = "[CRLF]"; + private static readonly String[] LineSeparators = new String[] { EnvironmentVariable.LineSeparator }; + + private Stages stage; + private EnvironmentVariableTarget scope; + private String start; + private String label; + private String value; + private String[] shift; + + public enum Stages + { + // Already existing and nothing applied yet. + Nothing, + + // Created by construction and should never change again. + Created, + + // Changed by property but should not overwrite an other status + Changed, + + // Overwrites any other status. + Deleted, + }; + + public EnvironmentVariable(EnvironmentVariableTarget scope) + { + this.scope = scope; + this.start = String.Empty; + this.label = "NEW"; + this.value = String.Empty; + this.shift = new String[0]; + this.stage = Stages.Created; + } + + public EnvironmentVariable(EnvironmentVariableTarget scope, DictionaryEntry entry) + { + String key = entry.Key != null ? entry.Key.ToString() : String.Empty; + String val = entry.Value != null ? entry.Value.ToString() : String.Empty; + + this.scope = scope; + this.start = key.Trim(); + this.label = key.Trim(); + this.value = val.Trim(); + this.shift = new String[0]; + this.stage = Stages.Nothing; + } + + private EnvironmentVariable(EnvironmentVariable other) + { + this.scope = other.scope; + this.start = other.start; + this.label = other.label; + this.value = other.value; + this.shift = other.shift; + this.stage = other.stage; + } + + public Stages Stage + { + get + { + return this.stage; + } + set + { + this.TryChangeStage(value); + } + } + + public EnvironmentVariableTarget Scope + { + get + { + return this.scope; + } + set + { + if (this.scope != value) + { + this.scope = value; + this.TryChangeStage(Stages.Changed); + } + } + } + + public String Start + { + get + { + return this.start; + } + } + + public String Label + { + get + { + return this.label; + } + set + { + value = (value ?? String.Empty).Trim(); + + if (this.label != value) + { + this.label = value; + this.TryChangeStage(Stages.Changed); + } + } + } + + public String Value + { + get + { + return this.value; + } + set + { + value = (value ?? String.Empty).Trim(); + + if (this.value != value) + { + this.value = value; + this.TryChangeStage(Stages.Changed); + } + } + } + + public String[] Shift + { + get + { + if (this.shift == null) + { + this.shift = new String[0]; + } + return this.shift; + } + set + { + value = this.GetArray(value); + + if (!Enumerable.SequenceEqual(this.shift, value)) + { + this.shift = value; + this.TryChangeStage(Stages.Changed); + } + } + } + + public Boolean IsCreated + { + get + { + return this.stage == Stages.Created; + } + } + + public Boolean IsChanged + { + get + { + return this.stage == Stages.Changed; + } + } + + public Boolean IsDeleted + { + get + { + return this.stage == Stages.Deleted; + } + } + + public Boolean IsRenamed + { + get + { + return this.IsModified && this.start.Length > 0 && String.Compare(this.start, this.label) != 0; + } + } + + public Boolean IsModified + { + get + { + return this.stage != Stages.Nothing; + } + } + + public Boolean IsReadonly + { + get + { + return this.scope == EnvironmentVariableTarget.Machine && !PermissionCheck.IsRunAsAdmin; + } + } + + public void LoadSettings(ConfigContent content) + { + if (content == null) + { + return; + } + + ConfigSection section = content.Find(EnvironmentVariable.ConfigSectionName); + + if (section == null) + { + return; + } + + ConfigValue value = section.Find(this.Label); + + if (value == null) + { + return; + } + + this.shift = this.IntoArray(value.Value); + } + + public void SaveSettings(ConfigContent content) + { + if (content == null) + { + return; + } + + ConfigSection section = content.Find(EnvironmentVariable.ConfigSectionName); + + if (section == null) + { + section = content.Append(EnvironmentVariable.ConfigSectionName); + } + + ConfigValue value = section.Find(this.Label); + + if (value == null) + { + value = new ConfigValue(this.Label); + } + + value.Value = this.FromArray(this.shift); + + section[this.Label] = value; + } + + public void FreeSettings(ConfigContent content) + { + if (content == null) + { + return; + } + + ConfigSection section = content.Find(EnvironmentVariable.ConfigSectionName); + + if (section == null) + { + return; + } + + if (!String.IsNullOrWhiteSpace(this.Start)) + { + section.Remove(this.Start); + } + + section.Remove(this.Label); + } + + public String GetTooltip() + { + String scope = this.scope.ToString(); + String stage = this.stage.ToString(); + String label = this.GetString(this.label); + String value = this.GetString(this.value); + + if (value.Length > 60) { value = value.Substring(0, 57) + "..."; } + + return + $"{nameof(this.Scope)}:\t{scope}{Environment.NewLine}" + + $"{nameof(this.Stage)}:\t{stage}{Environment.NewLine}" + + $"{nameof(this.Label)}:\t{label}{Environment.NewLine}" + + $"{nameof(this.Value)}:\t{value}"; + } + + public void TryChangeStage(Stages status) + { + if (this.stage != status) + { + // Delete overwrites any other status. + if (status == Stages.Deleted) + { + this.stage = Stages.Deleted; + } + + // May become Created, Changed or Deleted. + if (this.stage == Stages.Nothing) + { + this.stage = status; + } + } + } + + public Object Clone() + { + return new EnvironmentVariable(this); + } + + public override String ToString() + { + return + $"{nameof(this.Scope)}: {this.scope.ToString()}, " + + $"{nameof(this.IsCreated)}: {this.IsCreated}, " + + $"{nameof(this.IsChanged)}: {this.IsChanged}, " + + $"{nameof(this.IsDeleted)}: {this.IsDeleted}, " + + $"{nameof(this.IsRenamed)}: {this.IsRenamed}, " + + $"{nameof(this.IsModified)}: {this.IsModified}, " + + $"{nameof(this.IsReadonly)}: {this.IsReadonly}, " + + $"{nameof(this.Start)}: {this.GetString(this.start)}, " + + $"{nameof(this.Label)}: {this.GetString(this.label)}, " + + $"{nameof(this.Value)}: {this.GetString(this.value)}, " + + $"{nameof(this.Shift)}: {this.GetString(this.shift)}"; + } + + private String GetString(String value) + { + if (value == null) + { + return ""; + } + + if (value.Trim().Length < 1) + { + return ""; + } + + return value; + } + + private String GetString(String[] value) + { + if (value == null) + { + return ""; + } + + if (value.Length < 1) + { + return ""; + } + + return String.Join(EnvironmentVariable.LineSeparator, value); + } + + private String[] GetArray(String[] value) + { + return this.IntoArray(this.FromArray(value)); + } + + private String FromArray(String[] value) + { + if (value == null) + { + return String.Empty; + } + + if (value.Length < 1) + { + return String.Empty; + } + + return String.Join(EnvironmentVariable.LineSeparator, value); + } + + private String[] IntoArray(String value) + { + List result = new List(); + + if (!String.IsNullOrWhiteSpace(value)) + { + foreach (String current in value.Split(EnvironmentVariable.LineSeparators, StringSplitOptions.None)) + { + if (!String.IsNullOrWhiteSpace(current)) + { + result.Add(current.Trim()); + } + } + } + + return result.ToArray(); + } + } +} diff --git a/code/src/EnvironmentManager/Program.cs b/code/src/EnvironmentManager/Program.cs new file mode 100644 index 0000000..b13900b --- /dev/null +++ b/code/src/EnvironmentManager/Program.cs @@ -0,0 +1,322 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.ArgumentParser.Extensions; +using Plexdata.CfgParser.Converters; +using Plexdata.CfgParser.Entities; +using Plexdata.CfgParser.Processors; +using Plexdata.CfgParser.Settings; +using Plexdata.EnvironmentManager.Internals; +using Plexdata.EnvironmentManager.Models; +using Plexdata.LogWriter.Abstraction; +using Plexdata.LogWriter.Definitions; +using Plexdata.LogWriter.Extensions; +using Plexdata.LogWriter.Logging; +using Plexdata.LogWriter.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace Plexdata.EnvironmentManager +{ + static class Program + { + private static ILogger logger = null; + private static ConfigContent settings = null; + private static CommandArguments arguments = null; + + [STAThread] + static void Main() + { + Application.ThreadException += Program.OnUnhandledThreadException; + AppDomain.CurrentDomain.UnhandledException += Program.OnUnhandledDomainException; + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + if (Program.Arguments.IsHelp) + { + MessageBox.Show(Program.Arguments.Generate(70), "Help", MessageBoxButtons.OK, MessageBoxIcon.Question); + return; + } + + if (Program.Arguments.IsInstall) + { + Program.RunInstall(); + return; + } + + if (Program.Arguments.IsRemove) + { + Program.RunRemove(); + return; + } + + Application.Run(new MainForm()); + } + + internal static ILogger Logger + { + get + { + if (Program.logger == null) + { + Program.logger = new PersistentLogger(Program.GetLoggerSettings()); + } + + return Program.logger; + } + } + + internal static ConfigContent Settings + { + get + { + if (Program.settings == null) + { + Program.settings = ConfigReader.Read(Program.GetSettingsFilename()); + + if (!Program.settings.Header.IsValid) + { + Program.settings.Header = ConfigSettings.CreateDefaultHeader("Auto-generated configuration file.", true); + } + } + + return Program.settings; + } + } + + internal static CommandArguments Arguments + { + get + { + if (Program.arguments == null) + { + Program.arguments = new CommandArguments(); + List helper = new List(Environment.GetCommandLineArgs()); + + if (helper.Count > 1) + { + helper.RemoveAt(0); // Remove filename of executing assembly (required). + Program.arguments.Process(helper); + } + } + + return Program.arguments; + } + } + + internal static void SaveSettings() + { + if (Program.settings != null) + { + ConfigWriter.Write(Program.settings, Program.GetSettingsFilename(), true); + } + } + + internal static String ReadSettingsValue(String sectionName, String valueName) + { + if (String.IsNullOrWhiteSpace(sectionName) || String.IsNullOrWhiteSpace(valueName)) + { + return String.Empty; + } + + ConfigSection section = Program.Settings.Find(sectionName); + + if (section == null) + { + return String.Empty; + } + + ConfigValue value = section.Find(valueName); + + if (value == null) + { + return String.Empty; + + } + + return value.Value; + } + + internal static void SaveSettingsValue(String sectionName, String valueName, String valueData) + { + if (String.IsNullOrWhiteSpace(sectionName) || String.IsNullOrWhiteSpace(valueName)) + { + return; + } + + ConfigSection section = Program.Settings.Find(sectionName); + + if (section == null) + { + section = Program.Settings.Append(sectionName); + } + + ConfigValue value = section.Find(valueName); + + if (value == null) + { + value = new ConfigValue(valueName); + } + + value.Value = valueData ?? String.Empty; + + section[valueName] = value; + } + + private static void RunInstall() + { + if (!PermissionCheck.IsRunAsAdmin) + { + MessageBox.Show("This option requires administrator privileges.", "Install", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (TaskScheduler.IsInstalled()) + { + return; + } + + if (!TaskScheduler.Create()) + { + MessageBox.Show("Registration failed, task not created.", "Install", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static void RunRemove() + { + if (!PermissionCheck.IsRunAsAdmin) + { + MessageBox.Show("This option requires administrator privileges.", "Remove", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (!TaskScheduler.IsInstalled()) + { + return; + } + + if (!TaskScheduler.Delete()) + { + MessageBox.Show("Unregistration failed, Task possibly not completely removed.", "Remove", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static String GetSettingsFilename() + { + String filename = Path.ChangeExtension( + Path.GetFileName(Application.ExecutablePath), "ini"); + + String filepath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + typeof(Program).Namespace.Replace('.', Path.DirectorySeparatorChar)); + + if (!Directory.Exists(filepath)) + { + Directory.CreateDirectory(filepath); + } + + String fullname = Path.Combine(filepath, filename); + + if (!File.Exists(fullname)) + { + using (StreamWriter writer = File.CreateText(fullname)) + { + writer.Close(); + } + } + + return fullname; + } + + private static String GetLoggingFilename() + { + return Path.ChangeExtension(Program.GetSettingsFilename(), "log"); + } + + private static IPersistentLoggerSettings GetLoggerSettings() + { + ConfigSection section = Program.Settings.Find("Logging"); + + if (section == null) + { + section = Program.Settings.Append(new ConfigSection("Logging")); + section.Append(new ConfigValue("LogLevel", LogLevel.Trace.ToString())); + } + + Object logLevel = LogLevel.Trace; + if (!ValueConverter.TryConvert(section["LogLevel"].Value, typeof(LogLevel), out logLevel)) + { + logLevel = LogLevel.Trace; + } + + return new PersistentLoggerSettings() + { + ShowTime = true, + LogTime = LogTime.Utc, + LogType = LogType.Raw, + LogLevel = (LogLevel)logLevel, + Filename = Program.GetLoggingFilename(), + IsQueuing = false, + IsRolling = false, + Threshold = 0, + }; + } + + private static void OnUnhandledThreadException(Object sender, ThreadExceptionEventArgs args) + { + try + { + Exception error = args.Exception; + Program.Logger.Critical("Unhandled Thread Exception", error); + MessageBox.Show(error.Message, "Unhandled Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + catch (Exception exception) + { + MessageBox.Show(exception.ToString(), "Extreme Trouble", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + Application.Exit(); + } + + private static void OnUnhandledDomainException(Object sender, UnhandledExceptionEventArgs args) + { + try + { + Exception error = args.ExceptionObject as Exception; + Program.Logger.Critical("Unhandled Domain Exception", error); + MessageBox.Show(error.Message, "Unhandled Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + catch (Exception exception) + { + MessageBox.Show(exception.ToString(), "Extreme Trouble", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + Application.Exit(); + } + } +} diff --git a/code/src/EnvironmentManager/Properties/AssemblyInfo.cs b/code/src/EnvironmentManager/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3e38bb5 --- /dev/null +++ b/code/src/EnvironmentManager/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EnvironmentManager")] +[assembly: AssemblyDescription("This program allows to manage environment variables. Mainly, the program is designed to switch environment variables using the tray icon menu.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("plexdata.de")] +[assembly: AssemblyProduct("EnvironmentManager")] +[assembly: AssemblyCopyright("Copyright © 2019 - plexdata.de")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c1467758-5e28-4ba1-b416-d8e4685a5609")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/code/src/EnvironmentManager/Properties/Resources.Designer.cs b/code/src/EnvironmentManager/Properties/Resources.Designer.cs new file mode 100644 index 0000000..3f7eea7 --- /dev/null +++ b/code/src/EnvironmentManager/Properties/Resources.Designer.cs @@ -0,0 +1,163 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Plexdata.EnvironmentManager.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Plexdata.EnvironmentManager.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap CreateLargeIcon { + get { + object obj = ResourceManager.GetObject("CreateLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap DeleteLargeIcon { + get { + object obj = ResourceManager.GetObject("DeleteLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap DumpLargeIcon { + get { + object obj = ResourceManager.GetObject("DumpLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ExitLargeIcon { + get { + object obj = ResourceManager.GetObject("ExitLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap FollowLargeIcon { + get { + object obj = ResourceManager.GetObject("FollowLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon MainIcon { + get { + object obj = ResourceManager.GetObject("MainIcon", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ModifyLargeIcon { + get { + object obj = ResourceManager.GetObject("ModifyLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap SaveLargeIcon { + get { + object obj = ResourceManager.GetObject("SaveLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap SettingsLargeIcon { + get { + object obj = ResourceManager.GetObject("SettingsLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ShieldLargeIcon { + get { + object obj = ResourceManager.GetObject("ShieldLargeIcon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/code/src/EnvironmentManager/Properties/Resources.resx b/code/src/EnvironmentManager/Properties/Resources.resx new file mode 100644 index 0000000..0e31ba0 --- /dev/null +++ b/code/src/EnvironmentManager/Properties/Resources.resx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Images\create_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\delete_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\dump_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\exit_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\follow_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\MainIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\modify_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\save_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\settings_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\shield_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/Properties/Settings.Designer.cs b/code/src/EnvironmentManager/Properties/Settings.Designer.cs new file mode 100644 index 0000000..5e5cb62 --- /dev/null +++ b/code/src/EnvironmentManager/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Plexdata.EnvironmentManager.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/code/src/EnvironmentManager/Properties/Settings.settings b/code/src/EnvironmentManager/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/code/src/EnvironmentManager/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/code/src/EnvironmentManager/Serializers/EnvironmentSerializer.cs b/code/src/EnvironmentManager/Serializers/EnvironmentSerializer.cs new file mode 100644 index 0000000..0632cd6 --- /dev/null +++ b/code/src/EnvironmentManager/Serializers/EnvironmentSerializer.cs @@ -0,0 +1,136 @@ +/* + * MIT License + * + * Copyright (c) 2019 plexdata.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Plexdata.EnvironmentManager.Exceptions; +using Plexdata.EnvironmentManager.Models; +using Plexdata.LogWriter.Extensions; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Plexdata.EnvironmentManager.Serializers +{ + public static class EnvironmentSerializer + { + public static IEnumerable Load(EnvironmentVariableTarget target) + { + foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables(target)) + { + yield return EnvironmentSerializer.LoadSettings(new EnvironmentVariable(target, entry)); + } + } + + public static Boolean Save(IEnumerable variables, ref IList exceptions) + { + Int32 occurrences = 0; + + foreach (EnvironmentVariable variable in variables) + { + try + { + if (variable == null || !variable.IsModified) + { + continue; + } + + if (variable.IsReadonly) + { + throw new InvalidOperationException("Unable to process read-only variables."); + } + + EnvironmentSerializer.HandleCreated(variable); + + EnvironmentSerializer.HandleChanged(variable); + + EnvironmentSerializer.HandleDeleted(variable); + } + catch (Exception exception) + { + occurrences++; + + if (exceptions != null) + { + exceptions.Add(new EnvironmentException(variable, exception)); + } + + Program.Logger.Error("Error while saving environment variables.", exception); + } + } + + return occurrences == 0; + } + + private static void HandleCreated(EnvironmentVariable variable) + { + if (variable.IsCreated) + { + // Try creating this new environment variable. + Environment.SetEnvironmentVariable(variable.Label, variable.Value, variable.Scope); + variable.SaveSettings(Program.Settings); + } + } + + private static void HandleChanged(EnvironmentVariable variable) + { + if (variable.IsChanged) + { + if (variable.IsRenamed) + { + // Try discarding original environment variable. + Environment.SetEnvironmentVariable(variable.Start, String.Empty, variable.Scope); + variable.FreeSettings(Program.Settings); + } + + // Try creating this new environment variable. + Environment.SetEnvironmentVariable(variable.Label, variable.Value, variable.Scope); + variable.SaveSettings(Program.Settings); + } + } + + private static void HandleDeleted(EnvironmentVariable variable) + { + if (variable.IsDeleted) + { + if (variable.IsRenamed) + { + // Try discarding original environment variable. + Environment.SetEnvironmentVariable(variable.Start, String.Empty, variable.Scope); + } + else + { + // Try discarding current environment variable. + Environment.SetEnvironmentVariable(variable.Label, String.Empty, variable.Scope); + } + + variable.FreeSettings(Program.Settings); + } + } + + private static EnvironmentVariable LoadSettings(EnvironmentVariable variable) + { + variable.LoadSettings(Program.Settings); + return variable; + } + } +} diff --git a/code/src/EnvironmentManager/app.config b/code/src/EnvironmentManager/app.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/code/src/EnvironmentManager/app.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/code/src/EnvironmentManager/packages.config b/code/src/EnvironmentManager/packages.config new file mode 100644 index 0000000..5628120 --- /dev/null +++ b/code/src/EnvironmentManager/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file