@@ -1148,7 +1148,9 @@ public async Task<Guid> UpsertStepAsync(
11481148 // Check if step exists by name
11491149 var query = new QueryExpression ( SdkMessageProcessingStep . EntityLogicalName )
11501150 {
1151- ColumnSet = new ColumnSet ( SdkMessageProcessingStep . Fields . SdkMessageProcessingStepId ) ,
1151+ ColumnSet = new ColumnSet (
1152+ SdkMessageProcessingStep . Fields . SdkMessageProcessingStepId ,
1153+ SdkMessageProcessingStep . Fields . StateCode ) ,
11521154 Criteria = new FilterExpression
11531155 {
11541156 Conditions =
@@ -1199,10 +1201,25 @@ public async Task<Guid> UpsertStepAsync(
11991201 if ( ! string . IsNullOrEmpty ( stepConfig . RunAsUser ) &&
12001202 ! stepConfig . RunAsUser . Equals ( "CallingUser" , StringComparison . OrdinalIgnoreCase ) )
12011203 {
1202- if ( Guid . TryParse ( stepConfig . RunAsUser , out var userId ) )
1204+ Guid ? impersonatingUserId = null ;
1205+
1206+ if ( Guid . TryParse ( stepConfig . RunAsUser , out var parsedGuid ) )
1207+ {
1208+ impersonatingUserId = parsedGuid ;
1209+ }
1210+ else
12031211 {
1204- entity . ImpersonatingUserId = new EntityReference ( SystemUser . EntityLogicalName , userId ) ;
1212+ // Resolve username to GUID (by domain name or email)
1213+ impersonatingUserId = await ResolveUserIdAsync ( stepConfig . RunAsUser , client , cancellationToken ) ;
1214+ if ( impersonatingUserId == null )
1215+ {
1216+ throw new PpdsException (
1217+ ErrorCodes . Plugin . UserNotFound ,
1218+ $ "Could not resolve user '{ stepConfig . RunAsUser } '. Specify a valid GUID, domain name, or email address.") ;
1219+ }
12051220 }
1221+
1222+ entity . ImpersonatingUserId = new EntityReference ( SystemUser . EntityLogicalName , impersonatingUserId . Value ) ;
12061223 }
12071224
12081225 // Async auto-delete (only applies to async steps)
@@ -1211,23 +1228,41 @@ public async Task<Guid> UpsertStepAsync(
12111228 entity . AsyncAutoDelete = true ;
12121229 }
12131230
1231+ Guid stepId ;
12141232 if ( existing != null )
12151233 {
1216- entity . Id = existing . Id ;
1234+ stepId = existing . Id ;
1235+ entity . Id = stepId ;
12171236 await UpdateAsync ( entity , client , cancellationToken ) ;
12181237
12191238 // Add to solution even on update (handles case where component exists but isn't in solution)
12201239 if ( ! string . IsNullOrEmpty ( solutionName ) )
12211240 {
1222- await AddToSolutionAsync ( existing . Id , ComponentTypeSdkMessageProcessingStep , solutionName , cancellationToken ) ;
1241+ await AddToSolutionAsync ( stepId , ComponentTypeSdkMessageProcessingStep , solutionName , cancellationToken ) ;
12231242 }
12241243
1225- return existing . Id ;
1244+ // Handle state change for existing step if needed
1245+ var targetState = stepConfig . Enabled
1246+ ? sdkmessageprocessingstep_statecode . Enabled
1247+ : sdkmessageprocessingstep_statecode . Disabled ;
1248+ var currentState = existing . GetAttributeValue < OptionSetValue > ( SdkMessageProcessingStep . Fields . StateCode ) ? . Value ?? 0 ;
1249+ if ( currentState != ( int ) targetState )
1250+ {
1251+ await SetStepStateAsync ( stepId , targetState , client , cancellationToken ) ;
1252+ }
12261253 }
12271254 else
12281255 {
1229- return await CreateWithSolutionAsync ( entity , solutionName , client , cancellationToken ) ;
1256+ stepId = await CreateWithSolutionAsync ( entity , solutionName , client , cancellationToken ) ;
1257+
1258+ // New steps are created enabled by default - disable if needed
1259+ if ( ! stepConfig . Enabled )
1260+ {
1261+ await SetStepStateAsync ( stepId , sdkmessageprocessingstep_statecode . Disabled , client , cancellationToken ) ;
1262+ }
12301263 }
1264+
1265+ return stepId ;
12311266 }
12321267
12331268 /// <summary>
@@ -2170,6 +2205,61 @@ private static async Task<Entity> RetrieveAsync(
21702205 return await Task . Run ( ( ) => client . Retrieve ( entityName , id , columnSet ) , cancellationToken ) ;
21712206 }
21722207
2208+ /// <summary>
2209+ /// Resolves a username (domain name or email) to a systemuser GUID.
2210+ /// </summary>
2211+ /// <param name="username">The username, domain name, or email address.</param>
2212+ /// <param name="client">The Dataverse client.</param>
2213+ /// <param name="cancellationToken">Cancellation token.</param>
2214+ /// <returns>The user's GUID, or null if not found.</returns>
2215+ private static async Task < Guid ? > ResolveUserIdAsync (
2216+ string username ,
2217+ IOrganizationService client ,
2218+ CancellationToken cancellationToken )
2219+ {
2220+ username = username . Trim ( ) ;
2221+ var query = new QueryExpression ( SystemUser . EntityLogicalName )
2222+ {
2223+ ColumnSet = new ColumnSet ( SystemUser . Fields . SystemUserId ) ,
2224+ TopCount = 1
2225+ } ;
2226+
2227+ // Match by domain name OR internal email address
2228+ var filter = new FilterExpression ( LogicalOperator . Or ) ;
2229+ filter . AddCondition ( SystemUser . Fields . DomainName , ConditionOperator . Equal , username ) ;
2230+ filter . AddCondition ( SystemUser . Fields . InternalEMailAddress , ConditionOperator . Equal , username ) ;
2231+ query . Criteria . AddFilter ( filter ) ;
2232+
2233+ var result = await RetrieveMultipleAsync ( query , client , cancellationToken ) ;
2234+ return result . Entities . FirstOrDefault ( ) ? . Id ;
2235+ }
2236+
2237+ /// <summary>
2238+ /// Sets the state of a plugin step (enabled or disabled).
2239+ /// </summary>
2240+ /// <param name="stepId">The step ID.</param>
2241+ /// <param name="state">The target state.</param>
2242+ /// <param name="client">The Dataverse client.</param>
2243+ /// <param name="cancellationToken">Cancellation token.</param>
2244+ private static async Task SetStepStateAsync (
2245+ Guid stepId ,
2246+ sdkmessageprocessingstep_statecode state ,
2247+ IOrganizationService client ,
2248+ CancellationToken cancellationToken )
2249+ {
2250+ var statusCode = state == sdkmessageprocessingstep_statecode . Enabled
2251+ ? sdkmessageprocessingstep_statuscode . Enabled
2252+ : sdkmessageprocessingstep_statuscode . Disabled ;
2253+
2254+ var request = new SetStateRequest
2255+ {
2256+ EntityMoniker = new EntityReference ( SdkMessageProcessingStep . EntityLogicalName , stepId ) ,
2257+ State = new OptionSetValue ( ( int ) state ) ,
2258+ Status = new OptionSetValue ( ( int ) statusCode )
2259+ } ;
2260+ await ExecuteAsync ( request , client , cancellationToken ) ;
2261+ }
2262+
21732263 #endregion
21742264}
21752265
0 commit comments