From 8d85c63b72ed8f4dd59f20f33eb799902770bd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 11:03:06 +0100 Subject: [PATCH 1/8] Minor code optimization --- .../MA.Capabilities.cs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.Capabilities.cs b/Granfeldt.PowerShell.ManagementAgent/MA.Capabilities.cs index 6a3ece8..1568775 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.Capabilities.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.Capabilities.cs @@ -1,28 +1,24 @@ -using Microsoft.MetadirectoryServices; +// march 27, 2024 | soren granfeldt +// - code review and optimization + +using Microsoft.MetadirectoryServices; using System; namespace Granfeldt { public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password { - MACapabilities IMAExtensible2GetCapabilities.Capabilities - { - get - { - Tracer.Enter("capabilities"); - MACapabilities cap = new MACapabilities(); - cap.ConcurrentOperation = true; - cap.DeltaImport = true; - cap.DistinguishedNameStyle = MADistinguishedNameStyle.Generic; - cap.ExportType = MAExportType.ObjectReplace; - cap.FullExport = true; - cap.ObjectConfirmation = MAObjectConfirmation.Normal; - cap.ObjectRename = false; - cap.NoReferenceValuesInFirstExport = false; - Tracer.Exit("capabilities"); - return cap; - } - } - } + public MACapabilities Capabilities => new MACapabilities + { + ConcurrentOperation = true, + DeltaImport = true, + DistinguishedNameStyle = MADistinguishedNameStyle.Generic, + ExportType = MAExportType.ObjectReplace, + FullExport = true, + ObjectConfirmation = MAObjectConfirmation.Normal, + ObjectRename = false, + NoReferenceValuesInFirstExport = false + }; + } } From 788efbabadb73537996a00291c28fff7fdff947d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 11:04:03 +0100 Subject: [PATCH 2/8] Changed schema to interpret types from Dictionary Optimized debug message --- .../MA.Schema.cs | 284 +++++++++--------- 1 file changed, 139 insertions(+), 145 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.Schema.cs b/Granfeldt.PowerShell.ManagementAgent/MA.Schema.cs index 65c7d8f..b62f4ea 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.Schema.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.Schema.cs @@ -9,150 +9,144 @@ namespace Granfeldt { - public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password - { - Collection schemaResults; - string SchemaScript = null; - - class AttributeDefinition - { - public string Name { get; set; } - public AttributeType Type { get; set; } - public bool IsMultiValue { get; set; } - public bool IsAnchor { get; set; } - public bool ImportOnly { get; set; } - public bool ExportOnly { get; set; } - } - - Schema IMAExtensible2GetSchema.GetSchema(KeyedCollection configParameters) - { - Tracer.Enter("getschema"); - try - { - Schema schema = Schema.Create(); - InitializeConfigParameters(configParameters); - - OpenRunspace(); - Command cmd = new Command(Path.GetFullPath(SchemaScript)); - cmd.Parameters.Add(new CommandParameter("Username", Username)); - cmd.Parameters.Add(new CommandParameter("Password", Password)); - cmd.Parameters.Add(new CommandParameter("Credentials", GetSecureCredentials(Username, SecureStringPassword))); - - cmd.Parameters.Add(new CommandParameter("AuxUsername", UsernameAux)); - cmd.Parameters.Add(new CommandParameter("AuxPassword", PasswordAux)); - cmd.Parameters.Add(new CommandParameter("AuxCredentials", GetSecureCredentials(UsernameAux, SecureStringPasswordAux))); - - cmd.Parameters.Add(new CommandParameter("ConfigurationParameter", ConfigurationParameter)); - - schemaResults = InvokePowerShellScript(cmd, null); - CloseRunspace(); - - if (schemaResults != null) - { - foreach (PSObject obj in schemaResults) - { - string objectTypeName = null; - HashSet attrs = new HashSet(); - - foreach (PSPropertyInfo p in obj.Properties) - { - string[] elements = p.Name.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - string attrName = elements[0].Trim(); - string attrType = elements[1].Trim(); - - if (string.Equals(attrName, Constants.ControlValues.ObjectClass, StringComparison.OrdinalIgnoreCase)) - { - objectTypeName = p.Value.ToString(); - Tracer.TraceInformation("object-class '{0}'", objectTypeName); - } - else - { - AttributeDefinition ad = new AttributeDefinition(); - ad.Name = Regex.Replace(attrName, "^Anchor-", "", RegexOptions.IgnoreCase); - ad.Name = Regex.Replace(ad.Name, "^ImportOnly-", "", RegexOptions.IgnoreCase); - ad.Name = Regex.Replace(ad.Name, "^ExportOnly-", "", RegexOptions.IgnoreCase); - ad.IsAnchor = p.Name.StartsWith("anchor-", StringComparison.OrdinalIgnoreCase); - ad.ImportOnly = p.Name.StartsWith("importonly-", StringComparison.OrdinalIgnoreCase); - ad.ExportOnly = p.Name.StartsWith("exportonly-", StringComparison.OrdinalIgnoreCase); - ad.IsMultiValue = p.Name.EndsWith("[]", StringComparison.OrdinalIgnoreCase); - switch (attrType.Replace("[]", "").ToLower()) - { - case "boolean": - ad.Type = AttributeType.Boolean; - break; - case "binary": - ad.Type = AttributeType.Binary; - break; - case "integer": - ad.Type = AttributeType.Integer; - break; - case "reference": - ad.Type = AttributeType.Reference; - break; - case "string": - ad.Type = AttributeType.String; - break; - default: - ad.Type = AttributeType.String; - break; - } - Tracer.TraceInformation("name '{0}', isanchor: {1}, ismultivalue: {2}, importonly: {3}, exportonly: {4}, type: {5}", ad.Name, ad.IsAnchor, ad.IsMultiValue, ad.ImportOnly, ad.ExportOnly, ad.Type.ToString()); - attrs.Add(ad); - } - } - if (string.IsNullOrEmpty(objectTypeName)) - { - Tracer.TraceError("missing-object-class"); - throw new Microsoft.MetadirectoryServices.NoSuchObjectTypeException(); - } - - SchemaType objectClass = SchemaType.Create(objectTypeName, true); - foreach (AttributeDefinition def in attrs) - { - if (def.IsAnchor) - { - objectClass.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(def.Name, def.Type)); - } - else - { - var attrOperation = def.ExportOnly ? AttributeOperation.ExportOnly : - def.ImportOnly ? AttributeOperation.ImportOnly : AttributeOperation.ImportExport; - - if (def.IsMultiValue) - { - objectClass.Attributes.Add(SchemaAttribute.CreateMultiValuedAttribute(def.Name, def.Type, attrOperation)); - } - else - { - objectClass.Attributes.Add(SchemaAttribute.CreateSingleValuedAttribute(def.Name, def.Type, attrOperation)); - } - } - } - if (objectClass.AnchorAttributes.Count == 1) - { - schema.Types.Add(objectClass); - } - else - { - Tracer.TraceError("missing-anchor-definition-on-object"); - throw new Microsoft.MetadirectoryServices.AttributeNotPresentException(); - } - } - } - schemaResults.Clear(); - return schema; - } - catch (Exception ex) - { - Tracer.TraceError("getschema", ex); - throw; - } - finally - { - Tracer.Exit("getschema"); - } - } - - } + public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password + { + Collection schemaResults; + string SchemaScript = null; + + class AttributeDefinition + { + public string Name { get; set; } + public AttributeType Type { get; set; } + public bool IsMultiValue { get; set; } + public bool IsAnchor { get; set; } + public bool ImportOnly { get; set; } + public bool ExportOnly { get; set; } + } + + // define a dictionary to map attribute types to AttributeType enum values + Dictionary typeMappings = new Dictionary + { + { "boolean", AttributeType.Boolean }, + { "binary", AttributeType.Binary }, + { "integer", AttributeType.Integer }, + { "reference", AttributeType.Reference }, + { "string", AttributeType.String } + }; + + Schema IMAExtensible2GetSchema.GetSchema(KeyedCollection configParameters) + { + Tracer.Enter("getschema"); + try + { + Schema schema = Schema.Create(); + InitializeConfigParameters(configParameters); + + OpenRunspace(); + Command cmd = new Command(Path.GetFullPath(SchemaScript)); + cmd.Parameters.Add(new CommandParameter("Username", Username)); + cmd.Parameters.Add(new CommandParameter("Password", Password)); + cmd.Parameters.Add(new CommandParameter("Credentials", GetSecureCredentials(Username, SecureStringPassword))); + + cmd.Parameters.Add(new CommandParameter("AuxUsername", UsernameAux)); + cmd.Parameters.Add(new CommandParameter("AuxPassword", PasswordAux)); + cmd.Parameters.Add(new CommandParameter("AuxCredentials", GetSecureCredentials(UsernameAux, SecureStringPasswordAux))); + + cmd.Parameters.Add(new CommandParameter("ConfigurationParameter", ConfigurationParameter)); + + schemaResults = InvokePowerShellScript(cmd, null); + CloseRunspace(); + + if (schemaResults != null) + { + foreach (PSObject obj in schemaResults) + { + string objectTypeName = null; + HashSet attrs = new HashSet(); + + foreach (PSPropertyInfo p in obj.Properties) + { + string[] elements = p.Name.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + string attrName = elements[0].Trim(); + string attrType = elements[1].Trim(); + + if (string.Equals(attrName, Constants.ControlValues.ObjectClass, StringComparison.OrdinalIgnoreCase)) + { + objectTypeName = p.Value.ToString(); + Tracer.TraceInformation($"object-class '{objectTypeName}'"); + } + else + { + AttributeDefinition ad = new AttributeDefinition(); + ad.Name = Regex.Replace(attrName, "^(Anchor-|ImportOnly-|ExportOnly-)", "", RegexOptions.IgnoreCase); + + ad.IsAnchor = p.Name.StartsWith("anchor-", StringComparison.OrdinalIgnoreCase); + ad.ImportOnly = p.Name.StartsWith("importonly-", StringComparison.OrdinalIgnoreCase); + ad.ExportOnly = p.Name.StartsWith("exportonly-", StringComparison.OrdinalIgnoreCase); + ad.IsMultiValue = p.Name.EndsWith("[]", StringComparison.OrdinalIgnoreCase); + + // get the attribute type without array notation and convert to lowercase and + // set ad.Type based on the dictionary mapping or default to AttributeType.String + string cleanedAttrType = attrType.Replace("[]", "").ToLower(); + ad.Type = typeMappings.ContainsKey(cleanedAttrType) ? typeMappings[cleanedAttrType] : AttributeType.String; + + Tracer.TraceInformation($"name '{ad.Name}', isanchor: {ad.IsAnchor}, ismultivalue: {ad.IsMultiValue}, importonly: {ad.ImportOnly}, exportonly: {ad.ExportOnly}, type: {ad.Type}"); //, ad.Name, ad.IsAnchor, ad.IsMultiValue, ad.ImportOnly, ad.ExportOnly, ad.Type.ToString()); + attrs.Add(ad); + } + } + if (string.IsNullOrEmpty(objectTypeName)) + { + Tracer.TraceError("missing-object-class"); + throw new NoSuchObjectTypeException(); + } + + SchemaType objectClass = SchemaType.Create(objectTypeName, true); + foreach (AttributeDefinition def in attrs) + { + if (def.IsAnchor) + { + objectClass.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(def.Name, def.Type)); + } + else + { + var attrOperation = def.ExportOnly ? AttributeOperation.ExportOnly : + def.ImportOnly ? AttributeOperation.ImportOnly : AttributeOperation.ImportExport; + + if (def.IsMultiValue) + { + objectClass.Attributes.Add(SchemaAttribute.CreateMultiValuedAttribute(def.Name, def.Type, attrOperation)); + } + else + { + objectClass.Attributes.Add(SchemaAttribute.CreateSingleValuedAttribute(def.Name, def.Type, attrOperation)); + } + } + } + if (objectClass.AnchorAttributes.Count == 1) + { + schema.Types.Add(objectClass); + } + else + { + Tracer.TraceError("missing-anchor-definition-on-object"); + throw new AttributeNotPresentException(); + } + } + } + schemaResults.Clear(); + return schema; + } + catch (Exception ex) + { + Tracer.TraceError("getschema", ex); + throw; + } + finally + { + Tracer.Exit("getschema"); + } + } + + } } From 497a6a550b5bfcad2588ecbf051200f073fe2d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 11:12:14 +0100 Subject: [PATCH 3/8] Removed unused using --- Granfeldt.PowerShell.ManagementAgent/MA.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.cs b/Granfeldt.PowerShell.ManagementAgent/MA.cs index 3ad29a3..ea9f70d 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.cs @@ -110,13 +110,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Management.Automation; -using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; namespace Granfeldt { - static class Constants + static class Constants { public static class Parameters { From 4b9e6a92971c30b9aa4f43ebbd4e6e05b90fd078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 14:50:10 +0100 Subject: [PATCH 4/8] Removed obsolete using Compressed functions to single line Simplified invoke statement since it handles null for object input itself --- .../MA.PowerShell.cs | 374 ++++++++---------- 1 file changed, 164 insertions(+), 210 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.PowerShell.cs b/Granfeldt.PowerShell.ManagementAgent/MA.PowerShell.cs index 5d72406..d13dded 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.PowerShell.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.PowerShell.cs @@ -3,230 +3,184 @@ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Security.Principal; namespace Granfeldt { - public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password - { - Runspace runspace = null; - PowerShell powershell = null; + public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password + { + Runspace runspace = null; + PowerShell powershell = null; - void results_DataAdded(object sender, DataAddedEventArgs e) - { - PSDataCollection obj = (PSDataCollection)sender; - Tracer.TraceInformation("output-psdata-type {0}, {1}", e.Index, obj[e.Index].BaseObject.GetType()); - } - static void Error_DataAdded(object sender, DataAddedEventArgs e) - { - PSDataCollection err = (PSDataCollection)sender; - Tracer.TraceError("error id: {0}, message: {1}", err[e.Index].Exception == null ? -1 : err[e.Index].Exception.HResult, err[e.Index].FullyQualifiedErrorId, err[e.Index].ToString()); - } - static void Verbose_DataAdded(object sender, DataAddedEventArgs e) - { - Tracer.TraceInformation("verbose {0}", ((PSDataCollection)sender)[e.Index].ToString()); - } - static void Warning_DataAdded(object sender, DataAddedEventArgs e) - { - Tracer.TraceWarning("warning {0}", 1, ((PSDataCollection)sender)[e.Index]); + void results_DataAdded(object sender, DataAddedEventArgs e) + { + PSDataCollection obj = (PSDataCollection)sender; + Tracer.TraceInformation("output-psdata-type {0}, {1}", e.Index, obj[e.Index].BaseObject.GetType()); } - static void Debug_DataAdded(object sender, DataAddedEventArgs e) - { - Tracer.TraceInformation("debug {0}", ((PSDataCollection)sender)[e.Index].ToString()); - } - static void Progress_DataAdded(object sender, DataAddedEventArgs e) - { - Tracer.TraceInformation("progress {0}", ((PSDataCollection)sender)[e.Index].ToString()); - } - - Collection InvokePowerShellScript(Command command, PSDataCollection pipelineInput) - { - Tracer.Enter("invokepowershellscript"); - SetupImpersonationToken(); - - Collection results = new Collection(); - try - { - try - { - powershell.Streams.ClearStreams(); - powershell.Commands.Clear(); - powershell.Commands.AddCommand(command); + static void Error_DataAdded(object sender, DataAddedEventArgs e) + { + PSDataCollection err = (PSDataCollection)sender; + Tracer.TraceError("error id: {0}, message: {1}", err[e.Index].Exception == null ? -1 : err[e.Index].Exception.HResult, err[e.Index].FullyQualifiedErrorId, err[e.Index].ToString()); + } + static void Verbose_DataAdded(object sender, DataAddedEventArgs e) => Tracer.TraceInformation("verbose {0}", ((PSDataCollection)sender)[e.Index].ToString()); + static void Warning_DataAdded(object sender, DataAddedEventArgs e) => Tracer.TraceWarning("warning {0}", 1, ((PSDataCollection)sender)[e.Index]); + static void Debug_DataAdded(object sender, DataAddedEventArgs e) => Tracer.TraceInformation("debug {0}", ((PSDataCollection)sender)[e.Index].ToString()); + static void Progress_DataAdded(object sender, DataAddedEventArgs e) => Tracer.TraceInformation("progress {0}", ((PSDataCollection)sender)[e.Index].ToString()); + void Runspace_AvailabilityChanged(object sender, RunspaceAvailabilityEventArgs e) => Tracer.TraceInformation("runspace-availability {0}", e.RunspaceAvailability); + void Runspace_StateChanged(object sender, RunspaceStateEventArgs e) => Tracer.TraceInformation("runspace-state-changed-to {0}", e.RunspaceStateInfo.State); - if (pipelineInput != null) - { - Tracer.TraceInformation("pipeline-object-count {0:n0}", pipelineInput.Count); - } - //if (ShouldImpersonate()) - //{ - // using (WindowsIdentity.Impersonate(impersonationToken)) - // { - // Tracer.TraceInformation("start-invoke-script {0}", command.CommandText); - // if (pipelineInput != null) - // { - // powershell.Invoke(pipelineInput, results); - // } - // else - // { - // powershell.Invoke(null, results); - // } - // } - //} - //else - //{ + Collection InvokePowerShellScript(Command command, PSDataCollection pipelineInput) + { + Tracer.Enter("invokepowershellscript"); + SetupImpersonationToken(); - Tracer.TraceInformation("start-invoke-script {0}", command.CommandText); - if (pipelineInput != null) - { - powershell.Invoke(pipelineInput, results); - } - else - { - powershell.Invoke(null, results); - } - //} - Tracer.TraceInformation("end-invoke-script {0}", command.CommandText); - } - catch (RuntimeException e) - { - Tracer.TraceError("script-invocation-error", e); - Tracer.TraceError("script-invocation-inner-exception", e.InnerException != null ? e.InnerException : e); - Tracer.TraceError("script-invocation-inner-exception-message", e.InnerException != null ? e.InnerException.Message : "n/a"); - Tracer.TraceError("script-invocation-error-stacktrace", e.StackTrace); - throw; - } - finally - { - Tracer.TraceInformation("script-had-errors {0}", powershell.HadErrors); - } - } - catch (Exception ex) - { - Tracer.TraceError("invokepowershellscript", ex); - throw; - } - finally - { - RevertImpersonation(); - Tracer.Exit("invokepowershellscript"); - } - return results; - } + Collection results = new Collection(); + try + { + try + { + powershell.Streams.ClearStreams(); + powershell.Commands.Clear(); + powershell.Commands.AddCommand(command); - void OpenRunspace() - { - Tracer.Enter("openrunspace"); - try - { - if (runspace == null) - { - Tracer.TraceInformation("creating-runspace"); - runspace = RunspaceFactory.CreateRunspace(); - runspace.ApartmentState = System.Threading.ApartmentState.STA; - runspace.ThreadOptions = PSThreadOptions.Default; - runspace.StateChanged += Runspace_StateChanged; - runspace.AvailabilityChanged += Runspace_AvailabilityChanged; - Tracer.TraceInformation("created-runspace"); - } - else - { - Tracer.TraceInformation("existing-runspace-state '{0}'", runspace.RunspaceStateInfo.State); - } - if (runspace.RunspaceStateInfo.State == RunspaceState.BeforeOpen) - { - Tracer.TraceInformation("opening-runspace"); - runspace.Open(); - } - else - { - Tracer.TraceInformation("runspace-already-open"); - } - Tracer.TraceInformation("runspace-state '{0}'", runspace.RunspaceStateInfo.State); - if (runspace.RunspaceStateInfo.State == RunspaceState.Opened) - { - Tracer.TraceInformation("runspace-powershell-version {0}.{1}", runspace.Version.Major, runspace.Version.Minor); - } + if (pipelineInput != null) + { + Tracer.TraceInformation("pipeline-object-count {0:n0}", pipelineInput.Count); + } - if (powershell == null) - { - Tracer.TraceInformation("creating-powershell"); - powershell = PowerShell.Create(); - powershell.Runspace = runspace; + Tracer.TraceInformation("start-invoke-script {0}", command.CommandText); + powershell.Invoke(pipelineInput, results); + Tracer.TraceInformation("end-invoke-script {0}", command.CommandText); + } + catch (RuntimeException e) + { + Tracer.TraceError("script-invocation-error", e); + Tracer.TraceError("script-invocation-inner-exception", e.InnerException != null ? e.InnerException : e); + Tracer.TraceError("script-invocation-inner-exception-message", e.InnerException != null ? e.InnerException.Message : "n/a"); + Tracer.TraceError("script-invocation-error-stacktrace", e.StackTrace); + throw; + } + finally + { + Tracer.TraceInformation("script-had-errors {0}", powershell.HadErrors); + } + } + catch (Exception ex) + { + Tracer.TraceError("invokepowershellscript", ex); + throw; + } + finally + { + RevertImpersonation(); + Tracer.Exit("invokepowershellscript"); + } + return results; + } - Tracer.TraceInformation("powershell instanceid: {0}, runspace-id: {1}", powershell.InstanceId, powershell.Runspace.InstanceId); - Tracer.TraceInformation("powershell apartmentstate: {0}, version: {1}", powershell.Runspace.ApartmentState, powershell.Runspace.Version); + void OpenRunspace() + { + Tracer.Enter("openrunspace"); + try + { + if (runspace == null) + { + Tracer.TraceInformation("creating-runspace"); + runspace = RunspaceFactory.CreateRunspace(); + runspace.ApartmentState = System.Threading.ApartmentState.STA; + runspace.ThreadOptions = PSThreadOptions.Default; + runspace.StateChanged += Runspace_StateChanged; + runspace.AvailabilityChanged += Runspace_AvailabilityChanged; + Tracer.TraceInformation("created-runspace"); + } + else + { + Tracer.TraceInformation("existing-runspace-state '{0}'", runspace.RunspaceStateInfo.State); + } + if (runspace.RunspaceStateInfo.State == RunspaceState.BeforeOpen) + { + Tracer.TraceInformation("opening-runspace"); + runspace.Open(); + } + else + { + Tracer.TraceInformation("runspace-already-open"); + } + Tracer.TraceInformation("runspace-state '{0}'", runspace.RunspaceStateInfo.State); + if (runspace.RunspaceStateInfo.State == RunspaceState.Opened) + { + Tracer.TraceInformation("runspace-powershell-version {0}.{1}", runspace.Version.Major, runspace.Version.Minor); + } - // the streams (Error, Debug, Progress, etc) are available on the PowerShell instance. - // we can review them during or after execution. - // we can also be notified when a new item is written to the stream (like this): - powershell.Streams.ClearStreams(); - powershell.Streams.Error.DataAdded += new EventHandler(Error_DataAdded); - powershell.Streams.Verbose.DataAdded += new EventHandler(Verbose_DataAdded); - powershell.Streams.Warning.DataAdded += new EventHandler(Warning_DataAdded); - powershell.Streams.Debug.DataAdded += new EventHandler(Debug_DataAdded); - powershell.Streams.Progress.DataAdded += new EventHandler(Progress_DataAdded); + if (powershell == null) + { + Tracer.TraceInformation("creating-powershell"); + powershell = PowerShell.Create(); + powershell.Runspace = runspace; - Tracer.TraceInformation("created-powershell"); - } - } - catch (Exception ex) - { - Tracer.TraceError("openrunspace", ex); - throw; - } - finally - { - Tracer.Exit("openrunspace"); - } - } + Tracer.TraceInformation("powershell instanceid: {0}, runspace-id: {1}", powershell.InstanceId, powershell.Runspace.InstanceId); + Tracer.TraceInformation("powershell apartmentstate: {0}, version: {1}", powershell.Runspace.ApartmentState, powershell.Runspace.Version); - void Runspace_AvailabilityChanged(object sender, RunspaceAvailabilityEventArgs e) - { - Tracer.TraceInformation("runspace-availability {0}", e.RunspaceAvailability); - } + // the streams (Error, Debug, Progress, etc) are available on the PowerShell instance. + // we can review them during or after execution. + // we can also be notified when a new item is written to the stream (like this): + powershell.Streams.ClearStreams(); + powershell.Streams.Error.DataAdded += new EventHandler(Error_DataAdded); + powershell.Streams.Verbose.DataAdded += new EventHandler(Verbose_DataAdded); + powershell.Streams.Warning.DataAdded += new EventHandler(Warning_DataAdded); + powershell.Streams.Debug.DataAdded += new EventHandler(Debug_DataAdded); + powershell.Streams.Progress.DataAdded += new EventHandler(Progress_DataAdded); - void Runspace_StateChanged(object sender, RunspaceStateEventArgs e) - { - Tracer.TraceInformation("runspace-state-changed-to {0}", e.RunspaceStateInfo.State); - } - void CloseRunspace() - { - Tracer.Enter("closerunspace"); - try - { - if (powershell != null) - { - Tracer.TraceInformation("disposing-powershell"); - powershell.Runspace.Close(); - powershell.Dispose(); - Tracer.TraceInformation("disposed-powershell"); - } + Tracer.TraceInformation("created-powershell"); + } + } + catch (Exception ex) + { + Tracer.TraceError("openrunspace", ex); + throw; + } + finally + { + Tracer.Exit("openrunspace"); + } + } + void CloseRunspace() + { + Tracer.Enter("closerunspace"); + try + { + if (powershell != null) + { + Tracer.TraceInformation("disposing-powershell"); + powershell.Runspace.Close(); + powershell.Dispose(); + Tracer.TraceInformation("disposed-powershell"); + } - if (runspace != null) - { - Tracer.TraceInformation("runspace-state '{0}'", runspace.RunspaceStateInfo.State); - if (runspace.RunspaceStateInfo.State != RunspaceState.Closed) - { - Tracer.TraceInformation("removing-runspace-eventhandlers"); - runspace.StateChanged -= Runspace_StateChanged; - runspace.AvailabilityChanged -= Runspace_AvailabilityChanged; - Tracer.TraceInformation("removed-runspace-eventhandlers"); - Tracer.TraceInformation("dispose-runspace"); - runspace.Dispose(); // dispose also closes runspace - Tracer.TraceInformation("disposed-runspace"); - } - } - } - catch (Exception ex) - { - Tracer.TraceError("closerunspace", ex); - throw; - } - finally - { - Tracer.Exit("closerunspace"); - } - } + if (runspace != null) + { + Tracer.TraceInformation("runspace-state '{0}'", runspace.RunspaceStateInfo.State); + if (runspace.RunspaceStateInfo.State != RunspaceState.Closed) + { + Tracer.TraceInformation("removing-runspace-eventhandlers"); + runspace.StateChanged -= Runspace_StateChanged; + runspace.AvailabilityChanged -= Runspace_AvailabilityChanged; + Tracer.TraceInformation("removed-runspace-eventhandlers"); + Tracer.TraceInformation("dispose-runspace"); + runspace.Dispose(); // dispose also closes runspace + Tracer.TraceInformation("disposed-runspace"); + } + } + } + catch (Exception ex) + { + Tracer.TraceError("closerunspace", ex); + throw; + } + finally + { + Tracer.Exit("closerunspace"); + } + } - } + } } From e02252088820ff099e2522be9f3e2e8a3fb8bae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 14:51:54 +0100 Subject: [PATCH 5/8] Added ImportPageNumber to allow script for check for first import run (will be 0 at first paged import call) Changed two function to one-liners --- Granfeldt.PowerShell.ManagementAgent/MA.Import.cs | 14 +++++--------- .../Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.Import.cs b/Granfeldt.PowerShell.ManagementAgent/MA.Import.cs index 1b73438..04fff16 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.Import.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.Import.cs @@ -26,18 +26,13 @@ public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetC object pageToken; bool UsePagedImport = false; string ImportScript = null; + int ImportPageNumber = 0; List importResults; List csentryqueue = new List(); - public int ImportDefaultPageSize - { - get { return 100; } - } - public int ImportMaxPageSize - { - get { return 10000; } - } + public int ImportDefaultPageSize => 100; + public int ImportMaxPageSize => 10000; Schema schema; PSObject schemaPSObject; @@ -151,6 +146,7 @@ public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRu if (MoreToImport) { + ImportPageNumber++; MoreToImport = false; // make sure we set more-to-import to false; could be overwritten further down if pagedimports is true, though // on first call, we set customdata to value from last successful run @@ -170,6 +166,7 @@ public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRu cmd.Parameters.Add(new CommandParameter("OperationType", importOperationType.ToString())); cmd.Parameters.Add(new CommandParameter("UsePagedImport", UsePagedImport)); cmd.Parameters.Add(new CommandParameter("PageSize", ImportRunStepPageSize)); + cmd.Parameters.Add(new CommandParameter("ImportPageNumber", ImportPageNumber)); cmd.Parameters.Add(new CommandParameter("Schema", schemaPSObject)); Tracer.TraceInformation("setting-custom-data '{0}'", importRunStep.CustomData); @@ -188,7 +185,6 @@ public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRu if (UsePagedImport) { - // Tracer.TraceError("paged-import-not-supported-currently"); object moreToImportObject = powershell.Runspace.SessionStateProxy.GetVariable("MoreToImport"); if (moreToImportObject == null) { diff --git a/Granfeldt.PowerShell.ManagementAgent/Properties/AssemblyInfo.cs b/Granfeldt.PowerShell.ManagementAgent/Properties/AssemblyInfo.cs index 39c6f26..934e967 100644 --- a/Granfeldt.PowerShell.ManagementAgent/Properties/AssemblyInfo.cs +++ b/Granfeldt.PowerShell.ManagementAgent/Properties/AssemblyInfo.cs @@ -15,7 +15,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Goverco")] [assembly: AssemblyProduct("PowerShell Management Agent")] -[assembly: AssemblyCopyright("Copyright © Soren Granfeldt 2011-2022")] +[assembly: AssemblyCopyright("Copyright © Soren Granfeldt 2011-2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -38,5 +38,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("5.6")] -[assembly: AssemblyFileVersion("5.6.4.2022")] +[assembly: AssemblyFileVersion("5.6.0327.2024")] [assembly: AssemblyInformationalVersion("5.6")] From 3a3f7a5a351267fcfda1ce34eb57eaa45684bd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 14:57:47 +0100 Subject: [PATCH 6/8] Increased max export pagesize from 500 to 9999 One-lined two functions --- Granfeldt.PowerShell.ManagementAgent/MA.Export.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs b/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs index f4e6b8d..cbd6def 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs @@ -1,5 +1,7 @@ // july 5, 2018, soren granfeldt // - added schema psobject as parameter to export script +// march 27, 2024 | soren granfeldt +// - increased max export pagesize from 500 to 9999 using Microsoft.MetadirectoryServices; using System; @@ -14,7 +16,6 @@ namespace Granfeldt { - public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetCapabilities, IMAExtensible2GetSchema, IMAExtensible2GetParameters, IMAExtensible2CallImport, IMAExtensible2CallExport, IMAExtensible2Password { int exportBatchSize; @@ -23,15 +24,9 @@ public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetC OperationType exportType; Collection exportResults; - int IMAExtensible2CallExport.ExportDefaultPageSize - { - get { return 100; } - } - int IMAExtensible2CallExport.ExportMaxPageSize - { - get { return 500; } - } - void IMAExtensible2CallExport.OpenExportConnection(System.Collections.ObjectModel.KeyedCollection configParameters, Schema types, OpenExportConnectionRunStep exportRunStep) + int IMAExtensible2CallExport.ExportDefaultPageSize => 100; + int IMAExtensible2CallExport.ExportMaxPageSize => 9999; + void IMAExtensible2CallExport.OpenExportConnection(System.Collections.ObjectModel.KeyedCollection configParameters, Schema types, OpenExportConnectionRunStep exportRunStep) { Tracer.Enter("openexportconnection"); try From 34f4c3cd9953866bba11daed9f052e0ec208ffbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 15:09:32 +0100 Subject: [PATCH 7/8] Added a few comments to the installation script --- .../Install-PowerShellManagementAgent.ps1 | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Granfeldt.PowerShell.ManagementAgent/Install-PowerShellManagementAgent.ps1 b/Granfeldt.PowerShell.ManagementAgent/Install-PowerShellManagementAgent.ps1 index 7cd9940..c142228 100644 --- a/Granfeldt.PowerShell.ManagementAgent/Install-PowerShellManagementAgent.ps1 +++ b/Granfeldt.PowerShell.ManagementAgent/Install-PowerShellManagementAgent.ps1 @@ -1,13 +1,18 @@ -# october 11, 2015 | soren granfeldt -# - version 5.5.0.3501 -# november 8, 2015 | soren granfeldt -# - version 5.5.1.1017 -param -( -) +# Author: Soren Granfeldt +# Date: October 11, 2015 +# Version: 5.5.0.3501 +# Description: This script copies necessary files for Granfeldt PowerShell Management Agent to FIM/MIM installation folders. +# November 8, 2015 - Update +# Author: Soren Granfeldt +# Version: 5.5.1.1017 +# Description: Updated version of the script. + +# Define filenames for the PowerShell Management Agent files $maxmlfilename = "Granfeldt.PowerShell.ManagementAgent.xml" $mafilename = "Granfeldt.PowerShell.ManagementAgent.dll" + +# Attempt to retrieve the installation location of FIM/MIM from the registry try { $location = get-itemproperty "hklm:\software\microsoft\forefront identity manager\2010\synchronization service" -erroraction stop | select -expand location @@ -18,6 +23,7 @@ catch break } +# Append necessary paths to the installation location $location = join-path $location "synchronization service" write-debug "install-location: $location" $extensionsfolder = join-path $location "extensions" @@ -25,6 +31,7 @@ write-debug "install-location: $extensionsfolder" $packagedmafolder = join-path $location "uishell\xmls\packagedmas" write-debug "install-location: $packagedmafolder" +# Copy PowerShell Management Agent files to their respective folders write-debug "copying $mafilename to $extensionsfolder" copy-item "$mafilename" -destination "$extensionsfolder" write-debug "copying $maxmlfilename to $packagedmafolder" From b9a93505389a694500fa3a151202389754f7c809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Granfeldt?= Date: Wed, 27 Mar 2024 16:17:51 +0100 Subject: [PATCH 8/8] Added ImportPageNumber and ExportPageNumber --- Granfeldt.PowerShell.ManagementAgent/MA.Export.cs | 6 ++++++ Granfeldt.PowerShell.ManagementAgent/MA.Import.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs b/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs index cbd6def..f4e1a7d 100644 --- a/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs +++ b/Granfeldt.PowerShell.ManagementAgent/MA.Export.cs @@ -23,6 +23,7 @@ public partial class PowerShellManagementAgent : IDisposable, IMAExtensible2GetC string ExportScript = null; OperationType exportType; Collection exportResults; + int ExportPageNumber = 0; int IMAExtensible2CallExport.ExportDefaultPageSize => 100; int IMAExtensible2CallExport.ExportMaxPageSize => 9999; @@ -40,6 +41,8 @@ void IMAExtensible2CallExport.OpenExportConnection(System.Collections.ObjectMode Tracer.TraceInformation("export-type '{0}'", exportType); exportBatchSize = exportRunStep.BatchSize; Tracer.TraceInformation("export-batch-size '{0}'", exportBatchSize); + + ExportPageNumber = 0; } catch (Exception ex) { @@ -58,6 +61,8 @@ PutExportEntriesResults IMAExtensible2CallExport.PutExportEntries(IList exportPipeline = new PSDataCollection(); try { + ExportPageNumber++; + Command cmd = new Command(Path.GetFullPath(ExportScript)); cmd.Parameters.Add(new CommandParameter("Username", Username)); cmd.Parameters.Add(new CommandParameter("Password", Password)); @@ -71,6 +76,7 @@ PutExportEntriesResults IMAExtensible2CallExport.PutExportEntries(IList(); pageToken = ""; + ImportPageNumber = 0; OpenImportConnectionResults oicr = new OpenImportConnectionResults(); ImportRunStepPageSize = openImportRunStep.PageSize;