Synchronize a WebResources folder to CRM with Powershell


 

If you need to synchronize all webresource from a local folder tree to a CRM solution with a simple Powershell script, I have the solution! Sourire

Process

The Powershell script realizes the following operations:

  1. Load configuration file
  2. Connect to CRM
  3. Load files from specified folder which have been modified in give timeframe
  4. Process each file
    1. Check if file match to an existing Webresource
    2. If not
      1. Create the webresource according to the file extension
      2. Add webresource to given solution
    3. Else
      1. Check if webresource content has changed
      2. If content is different, update webresouce
  5. Publish modifications if changes have been detected

 

Prerequisites

The script couldn’t run properly without the following criterias:

  1. CRM user specified in configuration file need to be system administrator (create / update webresources, publish)
  2. You should provide an Assemblies folder that contains
    1. CRM SDK assemblies :
      1. Microsoft.Xrm.Sdk.dll
      2. Microsoft.Xrm.Client.dll
      3. Microsoft.Crm.Sdk.Proxy.dll
  3. Script must be run with elevated privileges

 

Configuration

The script use a configuration.xml file that provide the following parameters :

   1: <Configuration>
   2:     <CrmConnectionString>Url=http://crm/dev</CrmConnectionString>
   3:     <DeltaHours>15</DeltaHours>
   4:     <SolutionName>Solution1</SolutionName>
   5:     <SolutionPrefix>new_</SolutionPrefix>
   6:     <WebResourceFolderPath>c:\MCS\WebResouces</WebResourceFolderPath>
   7: </Configuration>
  • CrmConnectionString : Connection to CRM organization (More info : https://msdn.microsoft.com/en-us/library/gg695810.aspx)
  • DeltaHours : Number of hours before the execution time that determine if the file is considered as changed (prevent to process a lot of file if it is not necessary)
  • SolutionName : Name of the CRM solution where webresources should be added
  • SolutionPrefix : Prefix used by webresource name in solution
  • WebResourceFolderPath : Path to the folder that contains webresource files.

 

Script

   1: clear;
   2:  
   3: function Add-Crm-Sdk
   4: {
   5:     # Load SDK assemblies
   6:     Add-Type -Path "$PSScriptRoot\Assemblies\Microsoft.Xrm.Sdk.dll";
   7:     Add-Type -Path "$PSScriptRoot\Assemblies\Microsoft.Xrm.Client.dll";
   8:     Add-Type -Path "$PSScriptRoot\Assemblies\Microsoft.Crm.Sdk.Proxy.dll";
   9: }
  10:  
  11: function Get-Configuration()
  12: {
  13:     $configFilePath = "$PSScriptRoot\Configuration.xml";
  14:     $content = Get-Content $configFilePath;
  15:     return $content;
  16: }
  17:  
  18: function Get-WebResource
  19: {
  20:      PARAM
  21:     (
  22:         [parameter(Mandatory=$true)]$name
  23:     )
  24:  
  25:     $query = New-Object -TypeName Microsoft.Xrm.Sdk.Query.QueryExpression -ArgumentList "webresource";
  26:     $query.Criteria.AddCondition("name", [Microsoft.Xrm.Sdk.Query.ConditionOperator]::Equal, $name);
  27:     $query.ColumnSet.AddColumn("content");
  28:     $results = $service.RetrieveMultiple($query);
  29:     $records = $results.Entities;
  30:  
  31:     if($records.Count -eq 1)
  32:     {
  33:         return $records[0];
  34:     }
  35:     return $null;
  36: }
  37:  
  38: Add-Crm-Sdk;
  39: $config = Get-Configuration;
  40: $publishXmlRequest = "<importexportxml><webresources>";
  41:  
  42: # =======================================================
  43: # Crm Connection
  44: # =======================================================
  45: $crmConnectionString = $config.Configuration.CrmConnectionString;
  46: $crmConnection = [Microsoft.Xrm.Client.CrmConnection]::Parse($crmConnectionString);
  47: $service = New-Object -TypeName Microsoft.Xrm.Client.Services.OrganizationService -ArgumentList $crmConnection;
  48:  
  49: $d = Get-Date;
  50: Write-Host "$d - Deploy WebResources start" -ForegroundColor Cyan;
  51:  
  52: # =======================================================
  53: # Load last modified webresources and process files
  54: # =======================================================
  55: $deltaHours = [int]$config.Configuration.DeltaHours;
  56: $delta = $d.AddHours($deltaHours*-1);
  57:  
  58: $webResources = Get-ChildItem $config.Configuration.WebResourceFolderPath -recurse -include *.js, *.html, *.css, *.png, *.gif | where-object {$_.mode -notmatch "d"} | where-object {$_.lastwritetime -gt $delta} 
  59: $current = 0;
  60: $total = $webResources.Count;
  61: foreach($wr in $webResources)
  62: {    
  63:     $current++;
  64:     $percent = ($current/$total)*100;
  65:         
  66:     # =======================================================
  67:     # Handle prefix in file name
  68:     # =======================================================
  69:     $webResourcePath = $wr.FullName.ToString();
  70:     $webResourceName = $wr.Name.ToString();
  71:     $extension = $_.Extension;
  72:     if($webResourceName.StartsWith($config.Configuration.SolutionPrefix) -eq $false)
  73:     {
  74:         $position = $webResourcePath.LastIndexOf($config.Configuration.SolutionPrefix);
  75:         if($position -gt 0)
  76:         {
  77:             $webResourceName = $webResourcePath.Substring($position);
  78:             $webResourceName = $webResourceName.Replace('\', '/');
  79:         }
  80:     }
  81:  
  82:     Write-Host " - WebResource '$webResourceName' (Path : $webResourcePath) " -NoNewline;
  83:     Write-Progress -Activity "WebResource deployment" -Status "[$current/$total] WebResource '$webResourceName' (Path : $webResourcePath)" -PercentComplete $percent;
  84:              
  85:     # =======================================================
  86:     # Check webresource existence
  87:     # If not exists, create it
  88:     # Else update it
  89:     # =======================================================
  90:     $webResourceContentB64 = Get-Base64 $webResourcePath;    
  91:     $webresource = Get-WebResource $webResourceName;
  92:     if($webresource -eq $null)
  93:     {
  94:         #region Webresource creation
  95:         Write-Host " not found!" -ForegroundColor Yellow -NoNewline;
  96:         if(!$webResourceName.StartsWith($config.Configuration.SolutionPrefix))
  97:         {
  98:             Write-Host "ignored!" -ForegroundColor Gray;
  99:             continue;
 100:         }
 101:         Write-Host "=> Create it ..." -NoNewline;
 102:                            
 103:         # =======================================================
 104:         # Create webresource
 105:         # =======================================================
 106:         $wr =  New-Object -TypeName Microsoft.Xrm.Sdk.Entity -ArgumentList "webresource"
 107:         $wr["name"] = $webResourceName;
 108:         $wr["displayname"] = $webResourceName;
 109:         $wr["content"] = Get-Base64 $webResourcePath;
 110:  
 111:         switch ($extension.ToLower())
 112:         {
 113:             ".htm"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 1; }
 114:             ".html" { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 1; }
 115:             ".css"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 2; }
 116:             ".js"   { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 3; }
 117:             ".xml"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 4; }
 118:             ".png"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 5; }
 119:             ".jpg"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 6; }
 120:             ".jpeg" { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 6; }
 121:             ".gif"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 7; }
 122:             ".xap"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 8; }
 123:             ".xsl"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 9; }
 124:             ".ico"  { $wr["webresourcetype"] = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue -ArgumentList 10;}
 125:             default { Write-Host "Unkown webresource extension : $extension" -ForegroundColor Red; }
 126:         }
 127:         $wr["webresourcetype"] = [Microsoft.Xrm.Sdk.OptionSetValue]$wr["webresourcetype"]; 
 128:  
 129:         try
 130:         {
 131:             $id = $service.Create($wr);
 132:             $publishXmlRequest += [string]::Concat("<webresource>", $id, "</webresource>"); 
 133:             Write-Host "Done!" -ForegroundColor Green -NoNewline;            
 134:             $publish = $true;
 135:         }
 136:         catch [Exception]
 137:         {                
 138:             Write-Host "Failed! [Error : $_.Exception]" -ForegroundColor Red;
 139:             continue;
 140:         }
 141:         
 142:         
 143:         # =======================================================
 144:         # Add webresource to CRM Solution
 145:         # =======================================================
 146:         $solutionName = $config.Configuration.SolutionName;
 147:         Write-Host " => Add to solution '$solutionName'..." -NoNewline;
 148:  
 149:         $request = New-Object -TypeName Microsoft.Crm.Sdk.Messages.AddSolutionComponentRequest;
 150:         $request.AddRequiredComponents = $false;
 151:         $request.ComponentType = 61;
 152:         $request.ComponentId = $id;
 153:         $request.SolutionUniqueName = $solutionName;
 154:         try
 155:         {
 156:             $response = $service.Execute($request);
 157:             
 158:         }
 159:         catch [Exception]
 160:         {                
 161:             Write-Host "Failed! [Error : $_.Exception]" -ForegroundColor Red;
 162:             continue;
 163:         }            
 164:         Write-Host "Done!" -ForegroundColor Green;
 165:         
 166:         #endregion Webresource creation
 167:     }
 168:     else
 169:     {
 170:         #region Webresource update
 171:  
 172:         # =======================================================
 173:         # Update webresource if content is different
 174:         # =======================================================
 175:         $crmWebResourceContent = $webresource.Attributes["content"].ToString();
 176:         if($crmWebResourceContent -ne $webResourceContentB64)
 177:         {
 178:             $webresource["content"] = $webResourceContentB64;
 179:             try
 180:             {
 181:                 $service.Update($webresource);
 182:                 $publishXmlRequest += [string]::Concat("<webresource>", $webresource.Id, "</webresource>");
 183:                 $publish = $true;
 184:                 Write-Host "updated!" -ForegroundColor Green;
 185:             }
 186:             catch [Exception]
 187:             {                
 188:                 Write-Host "update failed! [Error : $_.Exception]" -ForegroundColor Red;
 189:                 continue;
 190:             }
 191:         }
 192:         else
 193:         {
 194:             Write-Host "ignored!" -ForegroundColor Gray;
 195:         }
 196:         #endregion Webresource update
 197:     }    
 198: }
 199: write-progress one one -completed;
 200:  
 201: # =======================================================
 202: # Publish modifications if necessary
 203: # =======================================================
 204:  if($publish)
 205: {    
 206:     $d = Get-Date;
 207:     Write-Host "$d - Publish start" -ForegroundColor Cyan;
 208:     $publishXmlRequest += "</webresources></importexportxml>";
 209:  
 210:     $publishRequest = New-Object -TypeName Microsoft.Crm.Sdk.Messages.PublishXmlRequest;
 211:     $publishRequest.ParameterXml = $publishXmlRequest;
 212:     $publishResponse = $service.Execute($publishRequest);
 213:  
 214:     $d = Get-Date;
 215:     Write-Host "$d - Publish stop" -ForegroundColor Cyan;
 216: }
 217:  
 218: $d = Get-Date;
 219: Write-Host "$d - Deploy WebResources stop" -ForegroundColor Cyan;

Download the full version

Comments (3)

  1. Anonymous says:

    This is a great idea, but I am trying to execute this command on a Windows 8.1 desktop, but am getting an error on this line:

    ———————

    $webResourceContentB64 = Get-Base64 $webResourcePath;

    Error:  Get-Base64 : The term 'Get-Base64' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name,

    ———-

    Do I need to do another import or something?

  2. Hi Chris,

    oups, I forgot this function in the script :

    function Get-Base64

    {

       PARAM

       (

           [parameter(Mandatory=$true)]$path

       )

       $content = [System.IO.File]::ReadAllBytes($path);

       $content64 = [System.Convert]::ToBase64String($content);

       return $content64;

    }

    Sorry

    1. IPKiller says:

      Added the function and created a github repo for the fixed solution located here: https://github.com/BmanthaBernard/CRM2016PowershellDevOps

      Mad props to Aymeric. This saves me a lot of time while building angular apps for inside of CRM.

Skip to main content