Thursday, December 18, 2025

Assign Teams with Roles of specific BU using Power shell script

 


<#

.SYNOPSIS

The latest cross BU role mapping feature helps easily mange the cross-bu role mapping, However, there's no tool for bulk cross-bu role assignment, with currently available tools mapping directly to the User's/team's BU. This script associates the users/teams with the roles of the specified BU.

  Assign Security Roles to Teams in Dataverse and relate the Field Security (Column Security) Profile

  "Field Service Resource" to those teams. PowerShell 5.1 compatible; robust paging; safe OData URLs.

  - Uses OAuth2 client credentials (ClientId/ClientSecret/TenantId) if AccessToken not provided.

  - Prefetches ALL Teams, Roles (with BU GUID), Business Units, and Field Security Profiles.

  - Associates roles via teamroles_association/$ref (fallback to teamroles/$ref).

  - Associates Field Security Profile via teamprofiles_association/$ref.

#>


param(

  [Parameter(Mandatory=$true)]

  [string]$url,  # e.g., https://yourorg.crm6.dynamics.com


  # Option A: Pre-supplied token (if omitted, client credentials are used)

  [string]$AccessToken,


  # Option B: Client credentials (used if AccessToken is empty)

  [Parameter(Mandatory=$true)]

  [string]$applicationId,

  [Parameter(Mandatory=$true)]

  [string]$clientSecret,

  [Parameter(Mandatory=$true)]

  [string]$tenantId

)


# -------------------------- CONFIG --------------------------

# Field Security (Column Security) Profile to relate to each team in TeamRoleMap

$FieldSecurityProfileName = "Field Service - Resource"

# ------------------------------------------------------------

# -------- Install --------

try {

  Install-Module Microsoft.Xrm.Data.PowerShell -RequiredVersion "2.8.21" -Force -Scope CurrentUser -AllowClobber

} catch { }

# -------- Install --------

# -------- Token acquisition (client credentials if needed) --------

function Get-AccessTokenWithClientCredentials([string]$tenantId, [string]$applicationId, [string]$clientSecret, [string]$url) {

  if ([string]::IsNullOrWhiteSpace($tenantId) -or [string]::IsNullOrWhiteSpace($applicationId) -or [string]::IsNullOrWhiteSpace($clientSecret)) {

    throw "Missing TenantId/ClientId/ClientSecret for client-credentials flow."

  }

  $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

  $body = @{

    grant_type    = "client_credentials"

    client_id     = $applicationId

    client_secret = $clientSecret

    scope         = "$url/.default"

  }

  try {

    $resp = Invoke-RestMethod -Method POST -Uri $tokenEndpoint -Body $body -ContentType "application/x-www-form-urlencoded"

    if (-not $resp.access_token) { throw "Token response missing access_token." }

    return $resp.access_token

  } catch {

    throw "Token acquisition failed: $($_.Exception.Message)"

  }

}


if ([string]::IsNullOrWhiteSpace($AccessToken)) {

  $AccessToken = Get-AccessTokenWithClientCredentials -tenantId $tenantId -applicationId $applicationId -clientSecret $clientSecret -url $url

}


# -------- Helpers --------

function Normalize([string]$s) {

  if ([string]::IsNullOrWhiteSpace($s)) { return "" }

  $t = $s.Trim()

  return $t 

}


# Standard headers for Dataverse Web API

$HeadersCommon = @{

  "Authorization"    = "Bearer $AccessToken"

  "Accept"           = "application/json"

  "OData-MaxVersion" = "4.0"

  "OData-Version"    = "4.0"

}

$JsonHeaders = $HeadersCommon.Clone()

$JsonHeaders["Content-Type"] = "application/json; charset=utf-8"


# --------Sample - TeamRoleMap (edit as needed) --------

$TeamRoleMap = @{

  "Operations" = @(

    @{ roleName = "Base Role";    buName = "Operations BU" }

    @{ roleName = "Base Role";    buName = "Finance BU" }

    @{ roleName = "Staff Role";   buName = "Operations BU" }

    @{ roleName = "Staff Role";   buName = "Finance BU" }

    @{ roleName = "Manager Role"; buName = "Finance BU" }

    @{ roleName = "Manager Role"; buName = "Operations BU" }

  )

  "Finance Team" = @(

    @{ roleName = "Base Role";  buName = "Operations BU" }

    @{ roleName = "Base Role";  buName = "Finance BU" }

    @{ roleName = "Staff Role"; buName = "Operations BU" }

    @{ roleName = "Staff Role"; buName = "Finance BU" }

  )

}


# -------- Paging helper (follows @odata.nextLink) --------

function Invoke-DvGetAll([string]$Uri) {

  $results = @()

  $next = $Uri

  while ($next) {

    try {

      $resp = Invoke-RestMethod -Method GET -Uri $next -Headers $HeadersCommon

    } catch {

      throw "GET failed for '$next': $($_.Exception.Message)"

    }

    if ($resp.value) { $results += $resp.value }

    $next = $resp.'@odata.nextLink'

  }

  return $results

}


# -------- Prefetch: Teams, Roles, BUs, Field Security Profiles --------

$teamsBase = "$url/api/data/v9.2/teams"

$rolesBase = "$url/api/data/v9.2/roles"

$busBase   = "$url/api/data/v9.2/businessunits"

$fspBase   = "$url/api/data/v9.2/fieldsecurityprofiles"  # entity set path (Web API ref)


$teams = Invoke-DvGetAll ($teamsBase + '?$select=teamid,name')

$roles = Invoke-DvGetAll ($rolesBase + '?$select=roleid,name,_businessunitid_value')

$businessUnits = Invoke-DvGetAll ($busBase + '?$select=businessunitid,name')

$fieldSecProfiles = Invoke-DvGetAll ($fspBase + '?$select=fieldsecurityprofileid,name')


# BU GUID -> Name map

$buMap = @{}

foreach ($b in $businessUnits) {

  if ($b.businessunitid) {

    $buName = ""

    if ($null -ne $b.name) { $buName = $b.name }

    $buMap[$b.businessunitid] = $buName

  }

}


# Team index: normalized name -> teamid

$teamIndex = @{}

foreach ($t in $teams) {

  $teamIndex[(Normalize $t.name)] = $t.teamid

}


# Role index keyed by "roleName\nbuName"

$roleIndex = @{}

foreach ($r in $roles) {

  $buName = ""

  if ($r._businessunitid_value -and $buMap.ContainsKey($r._businessunitid_value)) {

    $buName = $buMap[$r._businessunitid_value]

  }

  $key = "{0}`n{1}" -f (Normalize $r.name), (Normalize $buName)

  $roleIndex[$key] = $r.roleid

}


# Resolve Field Security Profile ID by name

$fspTarget = $null

$normFspName = Normalize $FieldSecurityProfileName

foreach ($fsp in $fieldSecProfiles) {

  if ((Normalize $fsp.name) -eq $normFspName) { $fspTarget = $fsp; break }

}

$fspId = $null

if ($fspTarget -and $fspTarget.fieldsecurityprofileid) {

  $fspId = [Guid]$fspTarget.fieldsecurityprofileid

  Write-Host ("Field Security Profile found: {0} ({1})" -f $FieldSecurityProfileName, $fspId)

} else {

  Write-Warning ("Field Security Profile not found: {0}. Teams will not be related to a profile." -f $FieldSecurityProfileName)

}


Write-Host ("Prefetched: teams={0}, roles={1}, BUs={2}, FSPs={3}" -f $teamIndex.Count, $roleIndex.Count, $buMap.Count, $fieldSecProfiles.Count)


# -------- Existing role assignments for a Team --------

function Get-TeamExistingRoleIds([Guid]$TeamId) {

  $set = New-Object System.Collections.Generic.HashSet[Guid]

  try {

    $url1 = "$url/api/data/v9.2/teams($TeamId)/teamroles_association" + '?$select=roleid,name'

    $rolesForTeam1 = Invoke-DvGetAll $url1

    if ($rolesForTeam1) { foreach ($rr in $rolesForTeam1) { if ($rr.roleid) { [void]$set.Add([Guid]$rr.roleid) } } }

    return $set

  } catch { Write-Warning ("teamroles_association fetch failed for TeamId {0}: {1}" -f $TeamId, $_.Exception.Message) }

  try {

    $url2 = "$url/api/data/v9.2/teams($TeamId)/teamroles" + '?$select=roleid,name'

    $rolesForTeam2 = Invoke-DvGetAll $url2

    if ($rolesForTeam2) { foreach ($rr in $rolesForTeam2) { if ($rr.roleid) { [void]$set.Add([Guid]$rr.roleid) } } }

  } catch { Write-Warning ("teamroles fetch failed for TeamId {0}: {1}" -f $TeamId, $_.Exception.Message) }

  return $set

}


# -------- Existing Field Security Profiles for a Team --------

function Get-TeamExistingFspIds([Guid]$TeamId) {

  $set = New-Object System.Collections.Generic.HashSet[Guid]

  try {

    # M:N nav on Team: teamprofiles_association  (Web API ref)

    $requestUrl = "$url/api/data/v9.2/teams($TeamId)/teamprofiles_association" + '?$select=fieldsecurityprofileid,name'

    $fsps = Invoke-DvGetAll $requestUrl

    if ($fsps) { foreach ($p in $fsps) { if ($p.fieldsecurityprofileid) { [void]$set.Add([Guid]$p.fieldsecurityprofileid) } } }

  } catch { Write-Warning ("teamprofiles_association fetch failed for TeamId {0}: {1}" -f $TeamId, $_.Exception.Message) }

  return $set

}


# -------- Associate a Role to a Team (POST $ref; fallback nav property) --------

function Associate-RoleTo-Team([Guid]$TeamId, [Guid]$RoleId) {

  $roleRef = "$url/api/data/v9.2/roles($RoleId)"

  try {

    $url1 = "$url/api/data/v9.2/teams($TeamId)/teamroles_association/`$ref"

    $bodyJson = @{ "@odata.id" = $roleRef } | ConvertTo-Json -Depth 1

    Invoke-RestMethod -Method POST -Uri $url1 -Headers $JsonHeaders -Body $bodyJson -ErrorAction Stop | Out-Null

    return

  } catch { Write-Warning ("Associate via teamroles_association failed: {0}" -f $_.Exception.Message) }

  try {

    $url2 = "$url/api/data/v9.2/teams($TeamId)/teamroles/`$ref"

    $bodyJson2 = @{ "@odata.id" = $roleRef } | ConvertTo-Json -Depth 1

    Invoke-RestMethod -Method POST -Uri $url2 -Headers $JsonHeaders -Body $bodyJson2 -ErrorAction Stop | Out-Null

    return

  } catch { throw "Associate via teamroles failed: $($_.Exception.Message)" }

}


# -------- Associate Field Security Profile to a Team (POST $ref) --------

function Associate-Fsp-To-Team([Guid]$TeamId, [Guid]$FspId) {

  if ($null -eq $FspId) { throw "FSP Id is null; cannot relate." }

  $fspRef = "$url/api/data/v9.2/fieldsecurityprofiles($FspId)"

  # Collection-valued nav associate (Web API associate doc)

  $requestUrl = "$url/api/data/v9.2/teams($TeamId)/teamprofiles_association/`$ref"

  $bodyJson = @{ "@odata.id" = $fspRef } | ConvertTo-Json -Depth 1

  Invoke-RestMethod -Method POST -Uri $requestUrl -Headers $JsonHeaders -Body $bodyJson -ErrorAction Stop | Out-Null

}


# -------- Drive assignment --------

$stats = [ordered]@{

  Teams = 0; AssignedRoles = 0; SkippedRoles = 0; RoleNotFound = 0; RoleFailures = 0;

  FspAssigned = 0; FspSkipped = 0; FspNotFound = 0; FspFailures = 0

}

$start = [datetime]::Now


foreach ($teamName in $TeamRoleMap.Keys) {

  $stats.Teams++

  $normTeam = Normalize $teamName

  $teamId = $teamIndex[$normTeam]


  Write-Host ""

  Write-Host ("-- {0} --" -f $teamName)


  if (-not $teamId) {

    $stats.RoleNotFound++

    Write-Warning ("Team not found: {0}" -f $teamName)

    continue

  }


  # Existing associations

  $existingRoles = Get-TeamExistingRoleIds -TeamId $teamId

  # Normalize roles to HashSet[Guid] (belt & suspenders)

  if ($existingRoles -isnot [System.Collections.Generic.HashSet[Guid]]) {

    $tmpR = New-Object System.Collections.Generic.HashSet[Guid]

    if ($null -ne $existingRoles) {

      if ($existingRoles -is [Guid]) { [void]$tmpR.Add([Guid]$existingRoles) }

      else { foreach ($g in $existingRoles) { try { [void]$tmpR.Add([Guid]$g) } catch { } } }

    }

    $existingRoles = $tmpR

  }


  $existingFsps = Get-TeamExistingFspIds -TeamId $teamId

  # Normalize FSPs to HashSet[Guid] (fixes Guid .Contains error)

  if ($existingFsps -isnot [System.Collections.Generic.HashSet[Guid]]) {

    $tmpF = New-Object System.Collections.Generic.HashSet[Guid]

    if ($null -ne $existingFsps) {

      if ($existingFsps -is [Guid]) { [void]$tmpF.Add([Guid]$existingFsps) }

      else { foreach ($g in $existingFsps) { try { [void]$tmpF.Add([Guid]$g) } catch { } } }

    }

    $existingFsps = $tmpF

  }


  # ---- Roles for this team ----

  $seen = New-Object System.Collections.Generic.HashSet[string]

  $desired = @()

  foreach ($entry in $TeamRoleMap[$teamName]) {

    $key = "{0}`n{1}" -f (Normalize $entry.roleName), (Normalize $entry.buName)

    if (-not $seen.Add($key)) { continue }

    $desired += [ordered]@{ roleName = $entry.roleName; buName = $entry.buName; key = $key }

  }


  foreach ($d in $desired) {

    $roleId = $roleIndex[$d.key]

    if (-not $roleId) {

      $stats.RoleNotFound++

      Write-Warning ("Role not found: '{0}' ({1})" -f $d.roleName, $d.buName)

      continue

    }

    $guidOk = $false; try { $null = [Guid]$roleId; $guidOk = $true } catch { $guidOk = $false }

    if (-not $guidOk) {

      $stats.RoleFailures++; Write-Warning ("Invalid roleId for '{0}' ({1}): '{2}'" -f $d.roleName, $d.buName, $roleId); continue

    }

    if ($existingRoles.Contains([Guid]$roleId)) {

      $stats.SkippedRoles++; Write-Host ("Skip (role already on team): '{0}' ({1})" -f $d.roleName, $d.buName); continue

    }

    try {

      Associate-RoleTo-Team -TeamId $teamId -RoleId ([Guid]$roleId)

      [void]$existingRoles.Add([Guid]$roleId)

      $stats.AssignedRoles++; Write-Host ("Role OK: '{0}' ({1})" -f $d.roleName, $d.buName)

    } catch {

      $stats.RoleFailures++; Write-Warning ("Role FAIL: '{0}' ({1}) - {2}" -f $d.roleName, $d.buName, $_.Exception.Message)

    }

  }


  # ---- Field Security Profile for this team ----

  if ($null -eq $fspId) {

    $stats.FspNotFound++

    Write-Warning ("Skip FSP relate: profile not found: {0}" -f $FieldSecurityProfileName)

  } else {

    if ($existingFsps.Contains([Guid]$fspId)) {

      $stats.FspSkipped++; Write-Host ("FSP Skip (already related): '{0}'" -f $FieldSecurityProfileName)

    } else {

      try {

        Associate-Fsp-To-Team -TeamId $teamId -FspId $fspId

        [void]$existingFsps.Add([Guid]$fspId)

        $stats.FspAssigned++; Write-Host ("FSP OK: '{0}'" -f $FieldSecurityProfileName)

      } catch {

        $stats.FspFailures++; Write-Warning ("FSP FAIL: '{0}' - {1}" -f $FieldSecurityProfileName, $_.Exception.Message)

      }

    }

  }

}


# ---- Elapsed time (as requested) ----

$secs = ([datetime]::Now - $start).TotalSeconds

Write-Host ""

Write-Host ("Done. Teams={0} Roles: Assigned={1} Skipped={2} NotFound={3} Failures={4} | FSP: Assigned={5} Skipped={6} NotFound={7} Failures={8} Time={9}s" -f `

  $stats.Teams, $stats.AssignedRoles, $stats.SkippedRoles, $stats.RoleNotFound, $stats.RoleFailures,

  $stats.FspAssigned, $stats.FspSkipped, $stats.FspNotFound, $stats.FspFailures, $secs)


Monday, December 8, 2025

SLA doesn't retrigger when the the criteria changes

Behavior:
Once SLA criteria is met, it reaches terminal status and no change to the status happens even if the criteria changes, resulting in newly applicable success conditions not met.

Steps to retrigger SLA when criteria Change:
From the Copilot Admin center App, Enable SLA Recalculate option.

Enabling SLA Recalculate ensures that:
  • The SLA is recalculated whenever the criteria change.
  • A new SLA instance is created, even if the previous SLA condition was already met and its success action executed.
  • This allows subsequent success actions tied to new criteria to run without manual intervention.

SLA is not triggering after solution import

Issue: SLA is not triggering after solution import 

Resolution: The SLAs remain inactive post import, hence
1.Manually activate and set as Default post every import

2. Exclude the SLAs from the solution after the first import, that will avoid this additional step.


Sunday, December 7, 2025

Field mapping is not working on Field Service mobile Online

Issue: This is a known limitation on Field Service Mobile online that the field mappings do not work.

Resolution:
Custom logic is needed to populate the fields.

-Ensure the tables being mapped are enabled for Mobile offline
-And Use Xrm.Webapi.offline.RetrieveRecord to retrieve the record and populate it

Email template image is blurred on import

Issue: The image pasted within an Email template - when moved to another organization - through solution blurs

Resolution : Add the images into a publicly accessible website and refer using href in the Email template

What will not work:
1. Adding it in a webresource - This wont render in the email, As this will need access to CRM.

2. Adding it on Power Pages - as a webtemplate - AS this will still need to go through security gates

Wednesday, February 26, 2025

Principles for Discovery and Design in Dynamics 365 System Implementation

When undertaking a Dynamics 365 implementation, whether it's for a new system or transitioning from an old system, the discovery and design sessions are pivotal. Here are principles for successful discovery and design sessions in any Dynamics 365 project:

1. Deep Business Process Analysis:

   - Conduct in-depth reviews of the client's business processes, employing advanced process mapping techniques to gain a holistic understanding of the operational landscape.

   - Utilize interactive workshops to extract nuanced insights and pinpoint opportunities for streamlining with Dynamics 365.

2. Maximization of Native Capabilities:

   - Prioritize the adoption of Dynamics 365's inherent features, promoting a configuration-first mindset.

   - Reserve custom development for scenarios where native solutions cannot fulfil critical business requirements.

3. Comprehensive Stakeholder Engagement:

   - Implement a structured engagement framework to ensure comprehensive input from a diverse group of stakeholders, fostering a sense of ownership and collaboration in every key decision-making process.

4. Objective-Driven Implementation Strategy:

   - Establish a set of well-defined, measurable goals for each phase of the project, that align with the strategic vision of the organization and the capabilities of Dynamics 365.

5. Adherence to Success by Design Principles:

   - Integrate Microsoft's Success by Design methodology to guide the implementation, leveraging its structured approach and industry insights. More details here: Success-by-Design

6. User-Centric Story Crafting:

   - Develop intricate user narratives that encapsulate the diverse interactions with Dynamics 365, focusing on intuitive user experiences and functional completeness.

7. Iterative Prototyping and Feedback Loops:

   - Employ a rapid prototyping model to construct and refine system designs, utilizing stakeholder feedback to iterate towards an optimal solution.

8. Rigorous Documentation Practice:

   - Maintain meticulous records of all requirements, decisions, and design iterations to serve as a comprehensive knowledge base for the project lifecycle.

Migration Strategy for Transitioning to Dynamics 365:

9. In-Depth Legacy System Evaluation:

   - Perform a detailed analysis of the legacy system, capturing its structure, data intricacies, and interdependencies to inform the migration strategy.

10. Strategic Migration Roadmap Development:

   - Craft a migration blueprint that delineates the migration's objectives, scope, and methodology, complemented by a phased timeline and critical checkpoints.

11. Data Mapping and Transformation:

   - Execute a thorough data mapping exercise to transition data to the new Dynamics 365 environment, ensuring data integrity through meticulous cleansing and transformation processes.

Wednesday, September 4, 2024

Considerations while moving Customer insight Journey Components

1. The Marketing Email templates, Emails, Content blocks can be added as solution components , however, if there's continuous updates made to the Emails/some Emails need to be deleted in the Target environment,  one might face circular dependency between Emails and content blocks.

Keep the Email templates, Emails, Content block out of solution and move them with Configuration Migration Tool

With this, they can be dealt with as data and can be deleted/Updated easily.

2. Journey import - A journey cannot be imported to an environment, if another version already exists in.

solution:

-The Journeys Should always imported with just the Journey Name and the Json. 

Including any other columns might cause the Publishing to fail.

3. Segment Import - A Segment already referred to in a Journey should not be updated.
The Journey referring to it should be Stopped, deleted, 
Segment should be deleted and re-imported.
Then the dependent Journey shall be re-imported.

Upgrade Considerations (On Prem - On Prem)

While assessing Upgrade of System, Key considerations:

Unsupported Code - Of JS and C# -
For On premise, can use a Code to check for references to the unsupported code.

 (For Online, can use the Power-apps solution checker)

Reference to the OOB internal tables/fields that are meant to change - Example - String Map and Object Type Code should be removed/updated to use the recommended ways of the Table Name and Option set instead refer

Reports - Use Shared Data Source, to minimize repetitive steps during deployment

Number Of fields exceeding the maximum allowed - while importing solution from source
(older version)
to the upgrade instance (Newer version) - happens as newer version might have an additional set of system fields 

Ensure there's no Unsupported Customization:
https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/gg328350(v=crm.8)?redirectedfrom=MSDN#Unsupported




Tuesday, November 29, 2022

Migrate User Query (Personal Views) using Kingswaysoft

 It took us to the UAT stage to be sure the View Migration using XRM tool box - Personal View Migration Tool might now be viable when there are 1000s of saved Views, and the Saved Views referring to deleted fields which needs extreme manual monitoring.

Thanks to friends Harshal and Nilesh, WE figured out a way to do from Kingsway soft

The userquery table doesn't appear in the source Connection's Tables list for Dynamics, we need to use the OLEDB connection, but the Destination can be Dynamics Connection itself.

The Migration will be 3 step

1. Migrate(Insert/Upsert) the Userqueries without the owner mapping.

2. Share the UserQueries with the Users using the Principal Object Access table migration(Insert/Upsert),
with Source Data filter - Entity name = userquery

3. Migrate the Owner of the User query so that they get assigned to the Original Owners (Upsert)

At Step 1 we do not map the Owner because, once the Owner is changed, the Share will not work, even if you have System Administrator role .

Also If we try to Update the changes to the View like the criteria or the columns, will not be able to update unless Package running User is the Owner.

The Object Type Code in the Layout xml will need to be updated to the userquery's entity in the Target.
(Else the View will not render, for the Custom Entities - where the Object Type code at the Source Organization and the Target Organization are different.)

Monday, November 28, 2022

CRM Managed solution import fail - Plugin does not contain required types

This happens as the Plugin Assembly is seen as a different one by CRM.
To resolve this:
1.Increase the version of the Solution and Publish at the source, then import to the destination


Thanks to Friend Seshagiri - For arriving at this Simple yet tricky solution

Import File is too Large to upload (On Premise)

 One of the scenarios this happens is:
The CRM App Server - where the CRM Application folder is present has low space.
(C>Program Files>Microsoft Dynamics)/

Clear the folder to have at least 1 GB free to resolve the issue.

On a side note, other issues that you may face due to Space issue in the App/DB server are- 
CRM stops working

Other Tools that try to connect to CRM will throw - Metadata reference cannot be resolved error.

Saturday, October 22, 2022

Power Pages or Power Portal Captcha not rendering when embedded in another website / basic or multistep form redirection not working

 When a Portal page is embedded within another website as an Iframe, the cookies used by the portal Page might be treated as a third party site cookies by the browser.

It is recommended that the domain name be a sibling or a child of the domain name of the site where you're embedding the portal in an iframe Example: your root website is www.contoso.com, the portal domain name should be portal.contoso.com

Reference: 
https://learn.microsoft.com/en-us/power-apps/maker/portals/embed-portal-website

CRM 2011 not rendering on the browser, inspite of opening in the compatibility View

Even after compatibility view, the CRM 2011 doesn't open, on load- and opening the debugger says "mainscript is undefined"

One of the scenarios this would happen if the Main Folder of the CRM is secured and has no access provided to the User opening CRM.
To resolve this, navigate to the drive where CRM was installed , navigate to the main folder and select Security, provide Read access to the Users,

User creation On Premise "Attempting to create a user with a domain logon that does not exist"


This can happen in some scenarios of a valid Username as below:
1. Trust is not established between the CRM Server domain and the User Domain
2. The User is not present is not active in Active Directory
3. Duplicate entry of User is present with same user Name in the Active Directory (Can happen in scenarios where Post deactivation, same Username is created again)

To resolve the issue, 
  •  Correct the User Name in the ACtive Directory by deleting the entry corresponding to the inactive username.
If it is not feasible to make the correction in the Active Directory, 
  • Temporarily turn the AutoGroupManagementOff to 1 in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM in CRM FrontEnd
    (This is by default 0 - And during any User creation in CRM they are automatically added to the Reporting Group and User Group)
  • Create User in CRM
  • Add the User manually to the ReportingGroup and the UserGroup of the corresponding CRM AD Group.

    For details refer: 
For details refer: https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/167494/issue-in-creating-a-user-in-crm-2011/411072


Tuesday, January 5, 2021

Set Optionset value transition validation

There are scenarios we want the option set field value has to transition in an order, for instance, if Low, Medium and High are the available labels of an option set field, and we want to restrict transitions so that from low, can transition only to medium, and from high only to medium. We would need some client side scripting for best UI experience to achieve this. 
We do have this OOB for Status reason, but not for option set fields.

We will need to have functions onLoad, onChange (OnRatingLevelChange) of the option set field and onSave , as named in the script.
On Load, we save the value in a variable which will be used for accessing the Pre - Change value.

Below is an example where in the valid transitions are stored in an object - ratingMapping for        Low<->Medium->High transition and accordingly the changes to the option set field are validated. 

// if HS namespace is not defiende create it.
if (typeof (myNamespace) == "undefined") { myNamespace = {}; }
myNamespace.optionValidation =
{   
    previousRatingLevel: null,
    OnLoad: function (executionContext) {
        var formContext = executionContext.getFormContext();
        this.SetOnLoadRatingLevel(formContext);
    },
    OnSave: function (executionContext) {
        var formContext = executionContext.getFormContext();
        if (this.ValidateRatingLevel(formContext)) {
            this.SetOnLoadRatingLevel(formContext);
        }
    },
    SetOnLoadRatingLevel: function (formContext) {
        previousRatingLevel = formContext.getAttribute("abc_ratinglevel").getValue();
    },
    OnRatingLevelChange: function (executionContext) {
        var formContext = executionContext.getFormContext();
        this.ValidateRatingLevel(formContext);
    },
    ValidateRatingLevel: function (formContext) {
        if (!!previousRatingLevel) {
            var validated = false;
            var lowStatus = 717710000;
            var mediumStatus = 717710001;
            var highStatus = 717710002;
            var ratingMapping = { 717710002: [highStatus, mediumStatus], 717710000: [lowStatus, mediumStatus], 717710001: [mediumStatus, lowStatus, highStatus] };
            var ratingLevelAttribute = formContext.getAttribute("abc_ratinglevel");
            var ratingLevelControl = formContext.getControl("abc_ratinglevel");
            var currentRatingLevel = ratingLevelAttribute.getValue();
            var ratingLevelErrorMessage = 'Please make Valid transition Low-> Medium, Medium -> Low / High, High -> Medium';
            var invalidRatingLevelTransitionMessageId = 'invalidRatingLevelTransitionMessageId';
            var timeout = 5000;
            if (!!ratingMapping[previousRatingLevel] && ratingMapping[previousRatingLevel].indexOf(currentRatingLevel) > -1) {
                validated = true;
                ratingLevelControl.clearNotification(invalidRatingLevelTransitionMessageId);
            }
            else {
                ratingLevelControl.setNotification(ratingLevelErrorMessage, invalidRatingLevelTransitionMessageId);
                setTimeout(function () {
                    ratingLevelControl.clearNotification(invalidRatingLevelTransitionMessageId);
                }, timeout);
                ratingLevelAttribute.setValue(previousRatingLevel);
                validated = false;
            }
            return validated;
        }
    }
}

Monday, January 4, 2021

Create Email in Power automate using Email address string / for unresolved recipients

WE come across scenarios where the Email recipients are not the CRM Users/ Contacts/ Queues which can get resolved when chosen in the Activity Party field as To. In such cases, we can use the addressused property of the activity Party to intake the email address string directly. 

In the below example, I am going to use a CRM user having a valid address as a sender and direct Email address for the 'To' field.

Steps:

1)Set the, "Allow messages with unresolved email recipients to be sent" option to Yes in System Settings. 





2)Set the sender variable with the record type and Guid(Optional, This can also be input directly in the step 4, or the Email address can also be set directly instead of record type-guid)

Store in a variable using compose step - this I did environment specific by storing in a custom config entity. and below is what I have for the dev environment.



systemusers/336e28c4-bdc4-ea11-a812-000d3a79607c

3)In the Create Email Action,  at the right top corner of Activity Party section, click on "Switch to detail inputs for an array item".


4)Input the string as below: 

[

  {

    "participationtypemask": 1,

    "partyid@odata.bind": @{variables('EmailSender')}

  },

  {

    "participationtypemask": 2,

    "addressused": "testemail@fabrikam.com"

  }

]


In the above expression,
   "participationtypemask": 1 corresponds to Sender, 

   "participationtypemask": 2 corresponds to recipient.

@{variables('EmailSender')}--> should resolve to the Email sender variable. Sender can also use "addressused" in place of "partyid@odata.bind" in case want to use Email address string instead of the user with guid.

Below is a snapshot

And We are ready! Save and Test the flow!!





Tuesday, September 8, 2020

LinPadQuery to connect to an org with username, password

 <Query Kind="Program">

  <NuGetReference>Microsoft.CrmSdk.CoreAssemblies</NuGetReference>
  <NuGetReference>Microsoft.CrmSdk.Deployment</NuGetReference>
  <NuGetReference>Microsoft.CrmSdk.Workflow</NuGetReference>
  <NuGetReference>Microsoft.CrmSdk.XrmTooling.CoreAssembly</NuGetReference>
  <NuGetReference>Microsoft.CrmSdk.XrmTooling.PackageDeployment</NuGetReference>
  <Namespace>Microsoft.Xrm.Sdk</Namespace>
  <Namespace>Microsoft.Xrm.Sdk.Messages</Namespace>
  <Namespace>Microsoft.Xrm.Sdk.Query</Namespace>
</Query>
 
string orgUrl = "https://.crm.dynamics.com/";
string authType = "Office365"; // AD, IFD, OAuth, Office365
string user = ".com";
string password = "#########";
string domain = ""; //not required
Microsoft.Xrm.Sdk.IOrganizationService _orgService;
Microsoft.Xrm.Sdk.ITracingService _tracer;
 
void Main()
{
    _orgService = CreateOrgService();
    var account = _orgService.RetrieveMultiple(new Microsoft.Xrm.Sdk.Query.FetchExpression($@"<fetch>
          <entity name='account'>
            <attribute name='name' />
            <attribute name='accountid' />
          </entity>
        </fetch>"));
 
    account[0].Id.Dump();
}
 
// Define other methods and classes here
Microsoft.Xrm.Sdk.IOrganizationService CreateOrgService()
{
    var connectionString = $"Url={orgUrl};AuthType={authType};RequireNewInstance=true";
    if (!string.IsNullOrEmpty(user))
    {
        connectionString += $";UserName={user}";
    }
    if (!string.IsNullOrEmpty(password))
    {
        connectionString += $";Password={password}";
    }
    if (!string.IsNullOrEmpty(domain))
    {
        connectionString += $";Domain={domain}";
    }
    connectionString.Dump();
    return new Microsoft.Xrm.Tooling.Connector.CrmServiceClient(connectionString);
}

Mock XmlHttpRequest /odata request's response in Jasmine Unit test, without using any other module

 WE first need to replace the XmlHttpRequest With a Custom one that we can use for responding to the calls:

Declare a variable in describe, to make it available in all the it nodes

let mockXHR 

In beforeEach add the below code. This replaces the XrmHttpRequestObject definition when triggered, keeping the original definition backup.

mockXHR = {
open: ()=>{console.log('open called from mock')},
send: ()=>{},
setRequestHeader:()=>{},
onreadystatechange:()=>{},
readyState:4,
status:200,
response:JSON.stringify(
              { cci_accountstatus:803230000 }
            ) 
          }; 
window["mockXHR"]=mockXHR;
constoldXMLHttpRequest = window.XMLHttpRequest;
window.XMLHttpRequest =function(){returnmockXHR;};
In it, Call the onReadyStateChange after calling the intended function to be tested.
exampleFunctionCallWhichMakesXHR();
 mockXHR.onreadystatechange();

In afterEach add below, so that the original definition for the XHR is restored after all the tests are executed.
afterEach(() => {     window.XMLHttpRequest = oldXMLHttpRequest;   });

 

Mock Xrm.WebApi.retrieveRecord(...).then in Jasmine Unit test without using any other node module

In the describe/beforeEach/it add below :

For Successful call:

 Xrm: {

  WebApi: {

        retrieveRecord: () => {
            //Add the result Object Properties to this object below
            var retrieveRecordResult = {
            };
            return Promise.resolve(retrieveRecordResult);
        }
        retrieveMultipleRecords: () => {
            //Add the result array Properties to this object below
            var retrieveMultipleRecordsResults = {
                entities: [{ fieldName: fieldValue, anotherFiedName: anotherFieldValue }]
            };
            return Promise.resolve(retrieveMultipleRecordsResults);
        }
    }
}
  window['Xrm']=Xrm;

For Error in the call:

Xrm: {
    WebApi: {
        retrieveRecord: () => {
            //Add the result Object Properties to this object below
            var retrieveRecordError = {
                message: 'Error in retrieve Multiple'
            };
            return Promise.reject(retrieveRecordError);
        }
        retrieveMultipleRecords: () => {
            //Add the result array Properties to this object below
            var retrieveMultipleRecordsError = {
                message: 'Error in retrieve Multiple'
            };
            return Promise.reject(retrieveMultipleRecordsError);
        }
    }
}
  window['Xrm']=Xrm;

Sunday, August 2, 2020

Check User Roles - Power Automate

We'd have come across requirements to check the roles of a User in Power automate, but not have found any message to get a User Roles directly from the flow. So the way to do it is to retrieve the System User: Role N:N intermediate entity records and check the count.

There's a different challenge going this way! the intermediate entity name to be used for list Records action in the flow is not same as the name that we see in the CRM solution, we need to get that from the API service. You can get the same by searching for the relation in the page: https://[OrganisationName].api.crm [Version] .dynamics.com/api/data/v9.0/         (Source : https://ajitpatra.com/2019/08/26/microsoft-flow-retrieve-nn-records-in-d365/)
For a System user roles the entity name is systemuserrolescollection

Below is the list records action fetch xml to retrieve No. of roles out of roleName1/roleName2/roleName3 that a User with guid "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" this guid can be passed directly or  can also be replaced with a variable as in the snapshot.
fetchxml for list records:

<fetch>
  <entity name="systemuserroles" >
    <attribute name="systemuserroleid" />
    <filter>
      <condition attribute="systemuserid" operator="eq" value="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />
    </filter>
    <link-entity name="role" from="roleid" to="roleid" link-type="inner" alias="Roles" >
      <filter type="or" >
        <condition attribute="name" operator="eq" value="roleName1" />
        <condition attribute="name" operator="eq" value="roleName2" />
        <condition attribute="name" operator="eq" value="roleName3 />
      </filter>
    </link-entity>
  </entity>
</fetch> 

The Length of the list - in this case  length(outputs('Retrieve_Creating_User_Roles_intent_roles_count')?['body']?['value']) returns non zero if User has at least one role out of above 3  mentioned. Replace Retrieve_Creating_User_Roles_intent_roles_count with the name of list records step.
And you are ready!