CRM

Generel information regarding CRM from infrastructure perspective.

Configure Azure Application Proxy application for CRM Internet Facing Deployment

The task was to configure an existing CRM IFD, with an existing ADFS / Azure Application Proxy infrastructure.

CRM IFD deployment was already working and implemented for internal access. What was missing was the external access through Azure Application Proxy.
Following this guide: https://azure.microsoft.com/en-us/documentation/articles/active-directory-application-proxy-publish/
With the following configuration will get you there:

Note that once you have the CRM Internet facing deployment done, no changes are required on CRM or ADFS. The below is only the Azure Application Proxy configuration required for CRM internet facing deployment.

Logon to manage.windowsazure.com and create a new Application under Active Directory.
Important configuration is:
– External URL: You can use the same URL as internally, however make sure that CNAME record is created as well as certificate is uploaded. This is referred to as custom domain.
– Preauthentication Method: Passthrough
– Translate URL in headers: No

You need to add required CRM IFD service url’s:
organisation, authentication and/or discovery service as seperate applications.

References
https://azure.microsoft.com/en-us/documentation/articles/active-directory-application-proxy-publish/
https://azure.microsoft.com/en-us/documentation/articles/active-directory-application-proxy-custom-domains/
https://azure.microsoft.com/en-us/documentation/articles/active-directory-application-proxy-claims-aware-apps/

Advertisements

CRM Server – Scripted installation

Yet another nice script from Mads Hjort Larsen.
I simply hadn’t found any automated installation of CRM server, including prerequisites SQL Reporting Services native mode and Watson components, so we decided to work this out. The script requires both SQL installation media as well as CRM Server media.

Script runtime is long, approximately 1 hour. That is just how long it takes to install all components. For reference, here is a video of a script run.
Video

NB: For some reason the parser is unable to parse to full script below.
Copy into Powershell ISE or similar will yield a fully color coded script.
NBB: This script has severe issues against named instances of SQL. We will issue an update once we have tested this.

Clear-Host
Set-Location $PSScriptRoot

#region Variables
$CRMgroupOU = '<CRM OU>' # Specifies the Active Directory organizational unit (OU) where the security groups will be created, for example, <OU> OU=CRM, DC=MySubDomain, DC=MyDomain, DC=com</OU>.
$SQLserver = '<SQL SERVER>'

#Media
$SQLmedia = 'D:' # Location of SQL server install media
$CRMmedia = 'E:' # Location of CRM server install media

$CRMupdateDir = "$PSScriptRoot\updates" # Directory that SERVER, ROUTER, SRS and MUI update files will be downloaded to.

#Accounts
$CRMSetupUser = '<CRM Setup User>' # Local Admin on CRM Server, Local Admin on SQL Server, Delegate Full Control on CRM Group OU
$CRMSetupUserPassword = '<CRM Setup User Password>'

$svcCRMApp = '<CRM AppPool account>' # Specifies the service account used to run the Microsoft Dynamics CRM Unzip Service and ASP.NET AppPool application pool identity.
$svcCRMAppPassword = '<CRM AppPool account password>'

$svcCRMDeploy = '<CRM Deployment account>' # Specifies the service account to use for the Deployment Web service.
$svcCRMDeployPassword = '<CRM Deployment account password>'

$svcCRMSandbox = '<CRM Sandbox account>' # Specifies the service account to use for the Sandbox Processing service.
$svcCRMSandboxPassword = '<CRM Sandbox account password>'

$svcCRMVSS = '<CRM VSS account>' # Specifies the service account to use for the Microsoft Dynamics CRM VSS Writer service.
$svcCRMVSSPassword = '<CRM VSS account password>'

$svcCRMAsync = '<CRM aSynch account>' # Specifies the service account to use for the asynchronous processing services.
$svcCRMAsyncPassword = '<CRM aSynch account password>'

$svcCRMMonitor = '<CRM Monitor account>' # Specifies the service account to use for the Microsoft Dynamics CRM Monitoring service.
$svcCRMMonitorPassword = '<CRM Monitor account password>'

$svcCRMEmailRouter = '<CRM Email router account>' # Specifies the service account to use for the Microsoft Dynamics CRM Email Router service.
$svcCRMEmailRouterPassword = 'CRM Email router account password>'

#ReportingServices
$RSappPoolAccount = '<SQL Reporting Services account>'
$RSappPoolAccountPassword = '<SQL Reporting Services account password>'

$RSdbname = $env:COMPUTERNAME+'_SQL_Reporting' # SQL Reporting Services database name.
$ReportingServicesURL = $env:COMPUTERNAME+":80" # SQL Reporting services URL (name and port)

$RSemailServer = "smtp.mycompany.com"
$RSemailAddress = "reporting.services@mycompany.co.uk"

$DBinstance = '<SQL Client Alias>' # Should be a SQL Client connection alias (Not SQL server alias).
$RSinstance = '' # If this value is left blank "MSSQLSERVER" will be used

#CRM XML
# https://technet.microsoft.com/en-us/library/hh699830.aspx
$patchUpdate = 'True' # Determines the behavior of the update Microsoft Dynamics CRM Server Setup technology. This feature lets Setup perform a one-time search for, and if applicable, download and apply the latest installation files for Microsoft Dynamics CRM.
$patchPath = '' # \\ServerName\ShareName\patchfile.msp

$licenseKey = '<License Key>' # Specifies the product key for this deployment. The configuration file can contain only one Microsoft Dynamics CRM product key.
$databaseCreate = 'true' # Values for this parameter are either true or false. True causes Setup to create a new Microsoft Dynamics CRM configuration database. False causes Setup to connect to an existing Microsoft Dynamics CRM configuration database.
$reportingUrl = "http://$env:COMPUTERNAME/ReportServer" # Specifies the URL of the Report Server.
$Collation = 'Latin1_General_CI_AI' # Specifies the SQL Server database collation name to use for the organization database. The default collation depends on the language of Microsoft Dynamics CRM Server that you’re installing, for example, Latin1_General_Cl_Al, which is the default collation for English (US) language deployments.

# After Setup is complete, you cannot change the base ISO currency code. However, you can change the base-currency name, base-currency symbol, and base-currency precision.
$currencycode="<ISO Currency code>" # Specifies the ISO three-letter currency-code, display name, and symbol to use for the base currency. For example, if you want to use U.S. dollars as the base currency, use isocurrencycode="USD". You must use a valid ISO currency description for the isocurrencycode attribute.
$currencyname="<Currency Name>" # You must also specify the currency-name and currency-symbol display names for the ISO base currency. For example, if the ISO currency code is USD, the currency name should be "US Dollar" and the currency symbol should be "$". However, you can use any string that you want for these attributes.
$currencysymbol="<Currency Symbol>" 
$currencyprecision="<Currency Precision>" # You must specify the precision for the base currency that you specified in the currencycode attribute. Valid values depend on the type of currency that you specify. For example, USD valid values are 1 – 9 and the default value is 2.

$displayName = '<CRM Display Name>' # Specifies the long name of your organization. The name can be up to 250 characters long and extended characters are supported.
$uniqueName = '<Organizational Name>' # Specifies the name of your organization in the URL that users will use to access the deployment. There is a 30 character limit. Extended characters or spaces are not allowed. If you don’t specify this element or leave the value blank, Setup will generate a short name based on the <Organization> element value.

$createNewWebsite = 'True' # Specifies the website to be used for Microsoft Dynamics CRM Server. Use Create="true" to create a new Microsoft Dynamics CRM website and leave the value $webSitePath blank.
$websitePort = '<CRM Port>' # Use port="TCPportnumber", where TCPportnumber is a valid and available TCP port number, to specify the port for connecting to the Microsoft Dynamics CRM Server application. If left blank, the port number that will be used is 5555. If $createNewWebsite="false", the port attribute is ignored.
$webSitePath = '' # Only needed if $createNewWebsite = 'false'

$SQMoptin = 'False' # Specifies whether you will participate in the Customer Experience Improvement Program.
$MUoptin = 'False' # Specifies whether to use Microsoft Update to download and install updates to Microsoft Dynamics CRM Server and other installed applications. After the installation is completed, this feature helps keep your computer up-to-date on an ongoing basis.

$ifdsettings = 'False' # This option should only be used for Internet-facing deployment. Set enabled = "true" to notify Microsoft Dynamics CRM server Setup to configure the deployment for Internet access. If the <ifdsettings> element is not specified, the enabled attribute value is set to false.
$internalnetworkaddress = '10.0.0.1-255.0.0.0' # IP address and subnet mask, such as 157.56.137.105-255.255.255.0. This is the internal IP address and the associated subnet mask of the subnet where your internal users reside. The subnets you enter here will be for the computers that you want to be considered as internal and you do not want the users to login through the IFD environment when they are on these subnets. To enter multiple subnets use a comma to separate the values in the configuration file Note: If you leave this element blank, all communication to the Microsoft Dynamics CRM server will be considered as internal and users will default to windows authentication when hitting the Microsoft Dynamics CRM website.
$rootdomainscheme = 'http' # Must be https, which will use secure sockets layer (SSL), or http, which will use the nonsecure HTTP protocol. Note: Setup does not require SSL on the Web site where Microsoft Dynamics CRM is installed. We strongly recommend that you specify the https value in the rootdomainscheme element. In addition, after Setup is complete, to help protect information that is transmitted between users and Microsoft Dynamics CRM Server, we recommend that you configure the Web site to require SSL. 
$sdkrootdomain = 'api.contoso.com' # Specifies the domain name that will be used for applications that use the methods described in the Microsoft Dynamics CRM 4.0 Software Development Kit (SDK). The value that is set here will be prefixed by your unique organization name to form the URL so you only need to put in the domain.com
$webapplicationrootdomain = 'app.contoso.com' # Specifies the domain name that will be used for the Microsoft Dynamics CRM Web application and Microsoft Dynamics CRM for Outlook. The value that is set here will be prefixed by your unique organization name to form the URL so you only need to put in the domain.com 
$discoveryrootdomain = 'disc.contoso.com' # The root domain for the discovery Web service.

$ExchangeServerName = '' # Specifies the Microsoft Exchange Server computer or POP3 that will be used by the Email Router to route incoming email messages. If not specified and later the Email Router is used in the deployment, the computer must be added to the PrivUserGroup security group.
$nonHTTPS = 'True' # If you do not want to use HTTPS, set this to true - see https://msdn.microsoft.com/en-us/library/hh550122(v=crm.6).aspx for more info.

#Updates
   $Server = 'http://download.microsoft.com/download/5/F/4/5F43956F-E1CF-4F15-96BE-967AF29E2005/CRM2015-Server-KB3010990-ENU-amd64.exe'
   $Router = 'http://download.microsoft.com/download/5/F/4/5F43956F-E1CF-4F15-96BE-967AF29E2005/CRM2015-Router-KB3010990-ENU-amd64.exe'
      $SRS = 'http://download.microsoft.com/download/5/F/4/5F43956F-E1CF-4F15-96BE-967AF29E2005/CRM2015-Srs-KB3010990-ENU-amd64.exe'
   $MUIENU = 'http://download.microsoft.com/download/5/F/4/5F43956F-E1CF-4F15-96BE-967AF29E2005/CRM2015-Mui-KB3010990-ENU-amd64.exe'
   $MUIDAN = 'http://download.microsoft.com/download/A/C/5/AC560071-42FF-44C0-AEE7-848FF99F74D9/CRM2015-Mui-DAN-amd64.exe'
$MUIDANUPD = 'http://download.microsoft.com/download/A/A/0/AA01E7CA-9120-48E4-8637-FFD8AD5E9C55/CRM2015-Mui-KB3056327-DAN-amd64.exe'
#endregion

##############################################################################
# It should not be neccessary to edit below this line ------------------- πŸ™‚ #
##############################################################################

$SQLinstallFile = $SQLmedia+'\Setup.exe'
$CRMinstallFile = $CRMmedia+'\Server\amd64\SetupServer.exe' # Location of SetupServer.exe
$SRSInstallFile = $CRMmedia+'\Server\amd64\SrsDataConnector\SetupSrsDataConnector.exe'
$EmailRouterInstallFile = $CRMmedia+'\EmailRouter\amd64\SetupEmailRouter.exe' # Location of Email Router setup file.
$ADserver = ([ADSI]”LDAP://RootDSE”).dnsHostName

function Add-Clock {
    $code = { 
    $start = Get-Date
    do {
        $now = Get-Date
        $diff = $now-$start
        $title = ("$diff").Split('.')[0].ToString()
        [System.Console]::Title = "Approx. time elapsed: "+$title
        Start-Sleep -Seconds 1
        } while ($true)
    }

$ps = [PowerShell]::Create()
$null = $ps.AddScript($code)
$ps.BeginInvoke()
}

if (!(Test-Path placeholder.tmp)) {
Add-Clock | Out-Null
Clear-Host
Write-Host ''
Write-Host ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘β–„β–„ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β•  β•šβ•β•β•β•β–ˆβ–ˆβ•‘' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘' -ForegroundColor Green
Write-Host ' β•šβ•β•     β•šβ•β•  β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•  β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β–€β–€β•β•  β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•   β•šβ•β•   β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•' -ForegroundColor Green
Write-Host ''

Write-Host 'Verifying SQL server setup...'
$Fulltext = Get-Service -ComputerName $sqlServer -DisplayName "SQL Full-Text*"
if ($Fulltext -eq $null){Write-Host "Full Text Search is not installed on $sqlServer" -ForegroundColor Red}
else{Write-Host $Fulltext.DisplayName "found on $SQLserver"}

$SQLAgent = Get-Service -ComputerName $sqlServer -DisplayName "SQL Server Agent*"
if ($SQLAgent -eq $null){Write-Host "SQL Agent is not installed on $sqlServer" -ForegroundColor Red}
else{Write-Host $SQLAgent.DisplayName "found on $SQLserver";Invoke-Command -ComputerName $sqlServer -ScriptBlock {Start-Service -Name SQLSERVERAGENT}}

if ($Fulltext -eq $null -OR $SQLAgent -eq $null){Exit}

New-Item placeholder.tmp -Type File -Force | Out-Null

#region HostsFileAndSQLAlias
Write-Host "`nAdding to Hosts file: " -NoNewline
$SQLServerIP = (Test-Connection -ComputerName $SQLserver -Count 1).IPV4Address.IPAddressToString

$hostsPath = "$env:windir\System32\drivers\etc\hosts"
$hosts = get-content $hostsPath
$hostLine = $SQLServerIP +' '+ $DBinstance
Write-Host $hostLine

if ($hosts -notcontains $hostLine){
    $newHosts = $hosts+$hostLine
    $newHosts | Out-File $hostsPath -Force
    }

# Check SQL alias
Write-Host "`nCreating TCP/IP Aliases`n"
Start-Process $env:windir\System32\cliconfg.exe
Start-Process $env:windir\sysWOW64\cliconfg.exe
Sleep 1
Stop-Process -Name cliconfg

# These are the two Registry locations for the SQL Alias locations
$x86 = "HKLM:\Software\Microsoft\MSSQLServer\Client\ConnectTo"
$x64 = "HKLM:\Software\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo"
 
# Test to see if the ConnectTo key already exists, and create it if it doesn't.
if ((test-path -path $x86) -ne $True){New-Item $x86 | Out-Null}
if ((test-path -path $x64) -ne $True){New-Item $x64 | Out-Null}
 
$TCPAlias = "DBMSSOCN," + $SQLserver
 
New-ItemProperty -Path $x86 -Name $DBinstance -PropertyType String -Value $TCPAlias -Force | Out-Null
New-ItemProperty -Path $x64 -Name $DBinstance -PropertyType String -Value $TCPAlias -Force | Out-Null
#endregion

#region CreateUsers
[ScriptBlock]$ScriptBlock = { 
  param(
    [string]$CRMgroupOU,
    [string]$CRMSetupUser,
    [string]$CRMSetupUserPassword, 
    [string]$svcCRMApp,
    [string]$svcCRMAppPassword, 
    [string]$svcCRMDeploy,
    [string]$svcCRMDeployPassword, 
    [string]$svcCRMSandbox,
    [string]$svcCRMSandboxPassword, 
    [string]$svcCRMVSS,
    [string]$svcCRMVSSPassword, 
    [string]$svcCRMAsync,
    [string]$svcCRMAsyncPassword, 
    [string]$svcCRMMonitor,
    [string]$svcCRMMonitorPassword, 
    [string]$svcCRMEmailRouter,
    [string]$svcCRMEmailRouterPassword, 
    [string]$RSappPoolAccount,
    [string]$RSappPoolAccountPassword
    )

    function AddUser ($user,$pass){
        if(!(Get-ADUser -Filter {sAMAccountname -eq $user})) {
            "Creating $user"
            New-ADUser -Name $user -AccountPassword (ConvertTo-SecureString -AsPlainText $pass -Force) -Enabled $true -Path $CRMgroupOU
            }
        else{"User $user already exists."}
    }

    if(!(Get-ADOrganizationalUnit -Filter {DistinguishedName -like $CRMgroupOU})){
        "Creating OU ($CRMgroupOU)"
        New-ADOrganizationalUnit -Name $CRMgroupOU.Split(',')[0].Split('=')[1] -Path ($CRMgroupOU.Split(',')[1..($CRMgroupOU.SPlit(',').Count)] -join ',')
        }
    else{"OU ($CRMgroupOU) already exists."}
    
    Write-Host "`nCreating accounts:"
    AddUser $CRMSetupUser $CRMSetupUserPassword
    AddUser $svcCRMApp $svcCRMAppPassword
    AddUser $svcCRMDeploy $svcCRMDeployPassword
    AddUser $svcCRMSandbox $svcCRMSandboxPassword
    AddUser $svcCRMVSS $svcCRMVSSPassword
    AddUser $svcCRMAsync $svcCRMAsyncPassword
    AddUser $svcCRMMonitor $svcCRMMonitorPassword
    AddUser $svcCRMEmailRouter $svcCRMEmailRouterPassword
    AddUser $RSappPoolAccount $RSappPoolAccountPassword
}

Invoke-Command -ComputerName $ADserver -ScriptBlock $ScriptBlock -ArgumentList $CRMgroupOU, $CRMSetupUser, $CRMSetupUserPassword, $svcCRMApp, $svcCRMAppPassword, $svcCRMDeploy, $svcCRMDeployPassword, $svcCRMSandbox, $svcCRMSandboxPassword, $svcCRMVSS, $svcCRMVSSPassword, $svcCRMAsync, $svcCRMAsyncPassword, $svcCRMMonitor, $svcCRMMonitorPassword, $svcCRMEmailRouter, $svcCRMEmailRouterPassword, $RSappPoolAccount, $RSappPoolAccountPassword
#endregion

#region LogonAsService
# http://anexinetisg.blogspot.dk/2014/02/grant-users-log-on-as-service-right-via.html
function Grant-LogOnAsService{
param(
    [string[]] $users
    ) 
 
    #Get list of currently used SIDs 
    secedit /export /cfg tempexport.inf | Out-Null
    $curSIDs = Select-String .\tempexport.inf -Pattern "SeServiceLogonRight" 
    $Sids = $curSIDs.line 
    $sidstring = ""
    foreach($user in $users){
        $objUser = New-Object System.Security.Principal.NTAccount($user)
        $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
        if(!$Sids.Contains($strSID) -and !$sids.Contains($user)){
            $sidstring += ",*$strSID"
        }
    }
    if($sidstring){
        $newSids = $sids + $sidstring
        Write-Host "`nNew Sids: " -NoNewline
        $newsids.Split(',')
        $tempinf = Get-Content tempexport.inf
        $tempinf = $tempinf.Replace($Sids,$newSids)
        Add-Content -Path tempimport.inf -Value $tempinf
        secedit /import /db secedit.sdb /cfg ".\tempimport.inf" | Out-Null
        secedit /configure /db secedit.sdb | Out-Null
 
        gpupdate /force | Out-Null
    }
    else{
        Write-Host "`nNo new sids"
    }
 
    Remove-Item ".\tempimport.inf" -Force -ErrorAction SilentlyContinue
    Remove-Item ".\secedit.sdb" -Force -ErrorAction SilentlyContinue
    Remove-Item ".\tempexport.inf" -Force -ErrorAction SilentlyContinue
}

Grant-LogOnAsService -users $svcCRMApp, $svcCRMDeploy, $svcCRMSandbox, $svcCRMVSS, $svcCRMAsync, $svcCRMMonitor
#endregion

#region Add-DomainUserToLocalGroup
# http://blogs.technet.com/b/heyscriptingguy/archive/2010/08/19/use-powershell-to-add-domain-users-to-a-local-group.aspx
function Add-DomainUserToLocalGroup { 
    [cmdletBinding()] 
    Param( 
        [Parameter(Mandatory=$True)] 
        [string]$computer, 
        [Parameter(Mandatory=$True)] 
        [string]$group, 
        [Parameter(Mandatory=$True)] 
        [string]$domain, 
        [Parameter(Mandatory=$True)] 
        [string]$user 
    ) 
    $de = [ADSI]"WinNT://$computer/$Group,group" 
    $de.psbase.Invoke("Add",([ADSI]"WinNT://$domain/$user").path) 
}

Add-DomainUserToLocalGroup -user $CRMSetupUser -group 'Administrators' -domain $env:USERDOMAIN -computer $env:COMPUTERNAME
Add-DomainUserToLocalGroup -user $CRMSetupUser -group 'Administrators' -domain $env:USERDOMAIN -computer $SQLserver

Add-DomainUserToLocalGroup -user $svcCRMApp -group 'Administrators' -domain $env:USERDOMAIN -computer $env:COMPUTERNAME
Add-DomainUserToLocalGroup -user $svcCRMApp -group 'Performance Log Users' -domain $env:USERDOMAIN -computer $env:COMPUTERNAME

Add-DomainUserToLocalGroup -user $svcCRMDeploy -group 'Administrators' -domain $env:USERDOMAIN -computer $env:COMPUTERNAME
Add-DomainUserToLocalGroup -user $svcCRMDeploy -group 'Administrators' -domain $env:USERDOMAIN -computer $SQLserver

Add-DomainUserToLocalGroup -user $svcCRMAsync -group 'Performance Log Users' -domain $env:USERDOMAIN -computer $env:COMPUTERNAME
#endregion

# Delegate Full Control on CRM Group OU
$ScriptBlock = [Scriptblock]::Create('dsacls "'+$CRMgroupOU+'"'+" /G $env:USERDOMAIN\$CRMSetupUser"+':GA /I:T')
Invoke-Command -ComputerName $ADserver -ScriptBlock $ScriptBlock | Out-Null

# Grant $CRMsetupUser SysAdm rights on the SQL server
$ScriptBlock = {
    param([string]$CRMSetupUser)
    Import-Module -Name SQLPS -DisableNameChecking
    Add-Type -AssemblyName "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"
    $smo = New-Object Microsoft.SqlServer.Management.Smo.Server $env:COMPUTERNAME
    $sqlUser = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList $smo,"$env:USERDOMAIN\$CRMSetupUser"
    $sqlUser.LoginType = 'WindowsUser'
    $sqlUser.PasswordPolicyEnforced = $false
    $sqlUser.Create()
    $smo.Logins["$env:USERDOMAIN\$CRMSetupUser"].AddToRole('sysadmin')
    }
Invoke-Command -ComputerName $SQLserver -ScriptBlock $ScriptBlock -ArgumentList $CRMSetupUser

#region ReportingServices
if ($RSinstance -eq ''){$RSinstance = 'MSSQLSERVER'}

$temp = $WarningPreference
$WarningPreference = 'SilentlyContinue'
Install-WindowsFeature –Name NET-Framework-Core
$WarningPreference = $temp

$ArgumentList = '/ACTION="Install" /FEATURES=RS /RSINSTALLMODE="FilesOnlyMode" /INSTANCENAME="'+$RSinstance+'" /INSTANCEID="'+$RSinstance+'" /ENU="True" /QUIETSIMPLE="True" /IACCEPTSQLSERVERLICENSETERMS /UpdateEnabled="True" /UpdateSource="MU" /ERRORREPORTING="False" /SQMREPORTING="'+$SQMoptin+'" /RSSVCACCOUNT='+$env:USERDOMAIN+'\'+$RSappPoolAccount+' /RSSVCPASSWORD='+$RSappPoolAccountPassword+' /RSSVCSTARTUPTYPE="Automatic"'
Start-Process $SQLinstallFile -ArgumentList $ArgumentList -Wait

Write-Host "`nGenerating Reporting Services configuration databases..."

$s = New-Object Management.ManagementScope("\\localhost\root\Microsoft\SqlServer\ReportServer\RS_$RSinstance\v12\admin")
$s.Connect()

$sc = New-Object Management.ManagementClass("\\localhost\root\Microsoft\SqlServer\ReportServer\RS_$RSinstance\v12\admin:MSReportServer_ConfigurationSetting")
$sc.Get()

$insts = $sc.GetInstances()
foreach ($o in $insts) { $inst = $o; }

# Set Virtual Directories
$inst.RemoveURL("ReportServerWebService", "http://$ReportingServicesURL", 1033) | Out-Null
$inst.RemoveURL("ReportManager", "http://$ReportingServicesURL", 1033) | Out-Null

$inst.SetVirtualDirectory("ReportServerWebService", "ReportServer", 1033) | Out-Null
$inst.SetVirtualDirectory("ReportManager", "ReportManager", 1033) | Out-Null

$inst.ReserveURL("ReportServerWebService", "http://$ReportingServicesURL", 1033) | Out-Null
$inst.ReserveURL("ReportManager", "http://$ReportingServicesURL", 1033) | Out-Null

# Create Reporting Services Database
$script = $inst.GenerateDatabaseCreationScript($RSdbname, 1033, $false)

[scriptblock]$scriptBlock = {
  param($script,$DBinstance)
    $script.Script | Out-File rs.sql
    sqlcmd -i rs.sql
    Remove-Item rs.sql
}
Invoke-Command -ComputerName $sqlServer -ScriptBlock $scriptBlock -ArgumentList $script,$DBinstance | Out-Null

$script = $inst.GenerateDatabaseRightsScript("$env:USERDOMAIN\$RSappPoolAccount", $RSdbname, $false, $true)

[scriptblock]$scriptBlock = {
  param($script,$DBinstance)
    $script.Script | Out-File rs.sql
    sqlcmd -i rs.sql
    Remove-Item rs.sql
}
Invoke-Command -ComputerName $sqlServer -ScriptBlock $scriptBlock -ArgumentList $script,$DBinstance | Out-Null

$inst.SetDatabaseConnection($DBinstance, $RSdbname, 2, "", "") | Out-Null
$inst.SetWindowsServiceIdentity($false, "$env:USERDOMAIN\$RSappPoolAccount", $RSappPoolAccountPassword) | Out-Null
#$inst.SetUnattendedExecutionAccount("<domain\user>", "<password>") | Out-Null
$inst.SetServiceState($true, $true, $true) | Out-Null
$inst.SetEmailConfiguration($true, $RSemailServer, $RSemailAddress) | Out-Null
#endregion

#region CRMserverPreReqs
Write-Host "`nInstalling CRM server prereqs:"
Write-Host "1of6 - Microsoft .NET Framework 4.5.2"
$file = gci ($CRMmedia+'\Redist\dotNETFX')
Start-Process $file.FullName -ArgumentList '/PASSIVE /NORESTART' -Wait

Write-Host "2of6 - Microsoft Application Error Reporting" 
$file = gci ($CRMmedia+'\Server\amd64\DW')
Start-Process $file.FullName -ArgumentList 'APPGUID=91710409-8000-11D3-8CFE-0150048383C9 /PASSIVE /NORESTART' -Wait

Write-Host "3of6 - SQL Native Client" 
$file = gci ($CRMmedia+'\Redist\SQLNativeClient')
Start-Process $file.FullName -ArgumentList '/PASSIVE /NORESTART' -Wait

Write-Host "4of6 - SQL Clr types" 
$file = gci ($CRMmedia+'\Redist\SQLSystemCLRTypes\*64*')
Start-Process $file.FullName -ArgumentList '/PASSIVE /NORESTART' -Wait

Write-Host "5of6 - SQL Server Management Objects" 
$file = gci ($CRMmedia+'\Redist\SQLSharedManagementObjects')
Start-Process $file.FullName -ArgumentList '/PASSIVE /NORESTART' -Wait

Write-Host "6of6 - Microsoft SQL Reporting Service Report Viewer Control" 
$file = gci ($CRMmedia+'\Redist\ReportViewer')
Start-Process $file.FullName -ArgumentList '/PASSIVE /NORESTART' -Wait
#endregion

#region AutoRun&AutoLogon
New-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce -Name "Test" -Value ("$pshome\powershell.exe -Command `"& `'$PSCommandPath`'`"") | Out-Null

Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name DefaultUserName -Value "$env:USERDOMAIN\$CRMSetupUser"
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name DefaultPassword -Value "$CRMSetupUserPassword"
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoAdminLogon -Value "1"
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name ForceAutoLogon -Value "1"
#endregion

Write-Host "`nRebooting..."
Restart-Computer
}
else{
Add-Clock | Out-Null
#region CRMinstall
Clear-Host
Write-Host ''
Write-Host '  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ–ˆβ•—    β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—     β–ˆβ–ˆβ•—     ' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•‘     ' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•‘     ' -ForegroundColor Green
Write-Host ' β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•‘     ' -ForegroundColor Green
Write-Host ' β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—' -ForegroundColor Green
Write-Host '  β•šβ•β•β•β•β•β•β•šβ•β•  β•šβ•β•β•šβ•β•     β•šβ•β•    β•šβ•β•β•šβ•β•  β•šβ•β•β•β•β•šβ•β•β•β•β•β•β•   β•šβ•β•   β•šβ•β•  β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•' -ForegroundColor Green
Write-Host ''

$xml = $null
$CRMxmlfile = "$PSScriptRoot\CRMSetupconfig.xml"

Write-Host 'Creating CRM Server XML answer file...'

$xml += '<CRMSetup>';$xml += "`r`n"
$xml += '<Server>';$xml += "`r`n"
$xml += '<Patch update="'+$patchUpdate+'">'+$patchPath+'</Patch>';$xml += "`r`n"
$xml += "<LicenseKey>$licenseKey</LicenseKey>";$xml += "`r`n"
$xml += "<SqlServer>$DBinstance</SqlServer>";$xml += "`r`n"
$xml += '<Database create="'+$databaseCreate+'"/>';$xml += "`r`n"
$xml += '<Reporting URL="'+$reportingUrl+'" />';$xml += "`r`n"
$xml += "<OrganizationCollation>$collation</OrganizationCollation>";$xml += "`r`n"
$xml += '<basecurrency isocurrencycode="'+$currencycode+'" currencyname="'+$currencyname+'" currencysymbol="'+$currencysymbol+'" currencyprecision="'+$currencyprecision+'"/>';$xml += "`r`n"
$xml += "<Organization>$displayName</Organization>";$xml += "`r`n"
$xml += "<OrganizationUniqueName>$uniqueName</OrganizationUniqueName>";$xml += "`r`n"
$xml += "<OU>$CRMgroupOU</OU>";$xml += "`r`n"
$xml += '<WebsiteUrl create="'+$createNewWebsite+'" port="'+$websitePort+'">'+$webSitePath+'</WebsiteUrl>';$xml += "`r`n"
$xml += "<InstallDir>C:\Program Files\Microsoft Dynamics CRM</InstallDir>";$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<CrmServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "   <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMApp</ServiceAccountLogin>" ;$xml += "`r`n"
$xml += "   <ServiceAccountPassword>$svcCRMAppPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</CrmServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<SandboxServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "  <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMSandbox</ServiceAccountLogin>";$xml += "`r`n"
$xml += "  <ServiceAccountPassword>$svcCRMSandboxPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</SandboxServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<DeploymentServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "  <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMDeploy</ServiceAccountLogin>";$xml += "`r`n"
$xml += "  <ServiceAccountPassword>$svcCRMDeployPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</DeploymentServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<AsyncServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "  <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMAsync</ServiceAccountLogin>";$xml += "`r`n"
$xml += "  <ServiceAccountPassword>$svcCRMAsyncPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</AsyncServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<VSSWriterServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "  <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMVSS</ServiceAccountLogin>";$xml += "`r`n"
$xml += "  <ServiceAccountPassword>$svcCRMVSSPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</VSSWriterServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<MonitoringServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += "  <ServiceAccountLogin>$env:USERDOMAIN\$svcCRMMonitor</ServiceAccountLogin>";$xml += "`r`n"
$xml += "  <ServiceAccountPassword>$svcCRMMonitorPassword</ServiceAccountPassword>";$xml += "`r`n"
$xml += '</MonitoringServiceAccount>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<SQM optin="'+$SQMoptin+'"/>';$xml += "`r`n"
$xml += '<muoptin optin="'+$MUoptin+'"/>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += '<!-- Settings for IFD installation. May be skipped for intranet-only deployment or to configure IFD later. -->';$xml += "`r`n"
$xml += ' <ifdsettings enabled="'+$ifdsettings+'">';$xml += "`r`n"
$xml += '      <!-- Define what address considered internal, required only if enabled=true -->';$xml += "`r`n"
$xml += '      <internalnetworkaddress>'+$internalnetworkaddress+'</internalnetworkaddress>';$xml += "`r`n"
$xml += '      <!-- Define URLs with IFD authentication, required only if enabled=true -->';$xml += "`r`n"
$xml += '      <rootdomainscheme>'+$rootdomainscheme+'</rootdomainscheme>';$xml += "`r`n"
$xml += '      <sdkrootdomain>'+$sdkrootdomain+'</sdkrootdomain>';$xml += "`r`n"
$xml += '      <webapplicationrootdomain>'+$webapplicationrootdomain+'</webapplicationrootdomain>';$xml += "`r`n"
$xml += '      <discoveryrootdomain>'+$discoveryrootdomain+'</discoveryrootdomain>';$xml += "`r`n"
$xml += ' </ifdsettings>';$xml += "`r`n"
$xml += '';$xml += "`r`n"
$xml += ' <Email>';$xml += "`r`n"
$xml += '  <IncomingExchangeServer name="'+$ExchangeServerName+'"/>';$xml += "`r`n"
$xml += ' </Email>';$xml += "`r`n"
$xml += '<LaunchReportingExtensionsSetup>False</LaunchReportingExtensionsSetup>';$xml += "`r`n"
$xml += ' </Server>';$xml += "`r`n"
$xml += '</CRMSetup>';$xml += "`r`n"

$xml | Out-File $CRMxmlfile -Force

Write-Host "Installing CRM server... pull up a chair, this part could take a while, like 30-40 mins :)`n"
Start-Process $CRMinstallFile -ArgumentList "/Q /config $CRMxmlfile" -Wait
Remove-Item placeholder.tmp
#endregion

#region SrsDataConnector
$xml = $null
$SRSxmlfile = "$PSScriptRoot\SrsDataConnectorSetupConfig.xml"

Write-Host 'Creating SrsDataConnector XML answer file...'

$xml += '<crmsetup>';$xml += "`r`n"
$xml += '<srsdataconnector>';$xml += "`r`n"
$xml += '<configdbserver>'+$SQLserver+'</configdbserver>';$xml += "`r`n"
$xml += '<autoupdateconfigdb>1</autoupdateconfigdb>';$xml += "`r`n"
$xml += '<reportserverurl>'+$reportingUrl+'</reportserverurl>';$xml += "`r`n"
$xml += '<autogroupmanagementoff>0</autogroupmanagementoff>';$xml += "`r`n"
$xml += '<instancename>'+$RSinstance+'</instancename>';$xml += "`r`n"
$xml += '<configsku>OnPremise</configsku>';$xml += "`r`n"
$xml += '<webstore enabled="false" configdb="false" />';$xml += "`r`n"
$xml += '<patch update="'+$patchUpdate+'" />';$xml += "`r`n"
$xml += '<muoptin optin="'+$MUoptin+'" />';$xml += "`r`n"
$xml += '<MonitoringServiceAccount type="DomainUser">';$xml += "`r`n"
$xml += '  <ServiceAccountLogin>'+$svcCRMMonitor+'</ServiceAccountLogin>';$xml += "`r`n"
$xml += '  <ServiceAccountPassword>'+$svcCRMMonitorPassword+'</ServiceAccountPassword>';$xml += "`r`n"
$xml += '</MonitoringServiceAccount>';$xml += "`r`n"
$xml += '</srsdataconnector>';$xml += "`r`n"
$xml += '</crmsetup>';$xml += "`r`n"
$xml | Out-File $SRSxmlfile

Write-Host " Installing SrsDataConnector...`n"
Start-Process $SRSInstallFile -ArgumentList "/Q /Config $SRSxmlfile" -Wait
#endregion

#region EmailRouter
# https://support.microsoft.com/en-us/kb/951401
Write-Host "Downloading Microsoft Exchange Server MAPI Client and Collaboration Data Objects 1.2.1"
$source = 'http://bumboks.dk/ExchangeMapiCdo.MSI'
$destination = "$PSScriptRoot\ExchangeMapiCdo.msi"
Invoke-WebRequest $source -OutFile $destination
Write-Host " Installing ExchangeMapiCdo.msi`n"
Start-Process $destination -ArgumentList '/passive' -Wait

$xml = $null
$EmailRouterSetupXMLfile = "$PSScriptRoot\EmailRouterSetupConfig.xml"

Write-Host 'Creating Email Router XML answer file...'

$xml += '<CRMSetup>';$xml += "`r`n"
$xml += '<EmailRouter>';$xml += "`r`n"
$xml += '<Features>';$xml += "`r`n"
$xml += '  <SinkService />';$xml += "`r`n"
$xml += '  <RulesWizard />';$xml += "`r`n"
$xml += '</Features>';$xml += "`r`n"
$xml += '<Patch update="'+$patchUpdate+'"></Patch>';$xml += "`r`n"
$xml += '<muoptin optin="'+$MUoptin+'" />';$xml += "`r`n"
$xml += '<InstallDir>C:\Program Files\Microsoft Dynamics CRM Email Router</InstallDir>';$xml += "`r`n"
$xml += '</EmailRouter>';$xml += "`r`n"
$xml += '</CRMSetup>';$xml += "`r`n"
$xml | Out-File $EmailRouterSetupXMLfile

Write-Host " Installing Email Router...`n"
Start-Process $EmailRouterInstallFile -ArgumentList "/Q /CONFIG $EmailRouterSetupXMLfile" -Wait

if($nonHTTPS -eq $true) {New-Item -Path HKLM:\Software\Microsoft\MSCRM\DisableSecureDecryptionKey -Value "1" | Out-Null}
else {New-Item -Path HKLM:\Software\Microsoft\MSCRM\DisableSecureDecryptionKey -Value "0" | Out-Null}

# Make the Windows Service ”Microsoft CRM Email Router” run under the svcCRMEmailRouter account.
$account="$env:USERDOMAIN\$svcCRMEmailRouter"
$service="displayname='Microsoft CRM Email Router'"

$svc=gwmi win32_service -filter $service
$svc.StopService() | Out-Null
$svc.change($null,$null,$null,$null,$null,$null,$account,$svcCRMEmailRouterpassword,$null,$null,$null) | Out-Null
$svc.StartService() | Out-Null
#endregion

#region updates
function DownloadUpdate ($type,$url) {
    $file = $url.Split('/')[-1]
    $dest = "$CRMupdateDir\$file"
    Write-Host "Downloading $file"
    $uri = New-Object "System.Uri" "$url"
    $request = [System.Net.HttpWebRequest]::Create($uri)
    $request.set_Timeout(5000)
    $response = $request.GetResponse()
    $totalLength = [System.Math]::Floor($response.get_ContentLength()/1024)
    $length = $response.get_ContentLength()
    $responseStream = $response.GetResponseStream()
    $destStream = New-Object -TypeName System.IO.FileStream -ArgumentList $dest, Create
    $buffer = New-Object byte[] 10KB
    $count = $responseStream.Read($buffer,0,$buffer.length)
    $downloadedBytes = $count
    while ($count -gt 0)
        {
        [System.Console]::CursorLeft=0
        [System.Console]::Write(" Downloaded {0}K of {1}K ({2}%)", [System.Math]::Floor($downloadedBytes/1024), $totalLength, [System.Math]::Round(($downloadedBytes / $length) * 100,0))
        $destStream.Write($buffer, 0, $count)
        $count=$responseStream.Read($buffer,0,$buffer.length)
        $downloadedBytes += $count
        }
    Write-Host ''
    $destStream.Flush()
    $destStream.Close()
    $destStream.Dispose()
    $responseStream.Dispose()
    
    New-Item "$CRMupdateDir\$type" -ItemType Directory | Out-Null
    Write-Host " Extracting... " -NoNewline
    Start-Process $CRMupdateDir\$file -ArgumentList "/extract:$CRMupdateDir\$type /passive" -Wait
    Write-Host "Installing... " -NoNewline
    if (Test-Path "$CRMupdateDir\$type\CrmUpdateWrapper.exe"){Start-Process "$CRMupdateDir\$type\CrmUpdateWrapper.exe" -ArgumentList "/quiet /norestart" -Verb RunAs -Wait}
    else{Start-Process msiexec.exe -ArgumentList "/i $CRMupdateDir\$type\MUISetup_1030_amd64.msi /quiet /norestart" -Verb RunAs -Wait}
    Write-Host "Done.`n"
    }

if (!(Test-Path $CRMupdateDir)) {New-Item $CRMupdateDir -ItemType Directory | Out-Null}
DownloadUpdate Server $Server
DownloadUpdate Router $Router
DownloadUpdate SRS $SRS
DownloadUpdate MUI-ENU $MUIENU
DownloadUpdate MUI-DAN $MUIDAN
DownloadUpdate MUI-DAN-UPD $MUIDANUPD
#endregion

#region MaintenancePlan 
# https://habaneroconsulting.com/insights/create-developer-sql-maintenance-plans-using-sql-agent-jobs-and-powershell
Invoke-Command -ComputerName $SQLserver -ScriptBlock {
    $JobName = ("{0}_SQL_Maintenance" -f $env:COMPUTERNAME)
    $DropExisting = $false
    $InstanceName = "MSSQLSERVER"
 
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
 
    # Get the Default Instance
    $srv = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Server
 
    # Get the SQL Agent Service for the Instance
    $Service = Get-Service -DisplayName ("SQL Server Agent ({0})" -f $InstanceName)
 
    # Set the SQL Agent Service startup type to Automatic and attempt to start it
    if($Service -eq $null){
        Throw ("Sql Sserver Agent ({0}) Not Found" -f $InstanceName)
    } else {
    Write-Host "Setting SQL Agent Startup type to Automatic"
        $Service | Set-Service -StartupType Automatic -ErrorAction Stop
        if($Service.Status -ieq "Stopped"){
            Write-Host "Agent Not Running"
            $Service.Start()
            Write-Host "Waiting for Service to Start." -NoNewline
            $Service.Refresh();
            While($Service.Status -ieq "StartPending"){
                $Service.Refresh()
                Write-Host "." -NoNewline
                Start-Sleep 2
            }
            if($Service.Status -ine "Running"){
                Write-Host ""
                Throw "An error occurred while starting `"SQL SERVER AGENT ($InstanceName)`". Try starting the service Manually. Then re-run this command."
            }
            Write-Host ".Started"
            $srv.JobServer.Refresh()
            Write-Host "Sleep for 10 Seconds While Agent wakes up..."
            Start-Sleep 10
            # Depending on the speed of your server, it may take longer than 10 seconds for the agent to wake up. You will know if the rest of the script fails.
        }
    }
    # Look for Existing Jobs with the same name.
    Try{
        $Jobs = $srv.JobServer.Jobs
        foreach($Job in $Jobs){
            if($Job.Name -ieq $JobName){
                Write-Host ("This Server already has a Job Called `'{0}`'." -f $JobName)
                if($DropExisting){
                    Write-Host ("Dropping Job: `'{0}`'." -f $JobName)
                    $Job.Drop()
                    break
                } else {
                    return
                }
            }
        }
    } Catch {
        Throw $_
    }
 
    # Define a Job object variable by supplying the Agent and the name arguments in the constructor and setting properties.
    $job = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Agent.Job -argumentlist $srv.JobServer, $JobName
 
    # Create the job on the instance of SQL Server Agent.
    Write-Host ("Creating job `'{0}`'." -f $JobName)
    $job.Create()
    $job.ApplyToTargetServer("(local)") #Job will run against the Agent's Local Server
 
    # Define a JobStep object variable by supplying the parent job and name arguments in the constructor.
    $jobstep = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Agent.JobStep -argumentlist $job, "Clean Up"

    $jobstep.Command = "SET QUOTED_IDENTIFIER ON 

    IF EXISTS (SELECT name from sys.indexes
                      WHERE name = N'CRM_AsyncOperation_CleanupCompleted')
          DROP Index AsyncOperationBase.CRM_AsyncOperation_CleanupCompleted
    GO
    CREATE NONCLUSTERED INDEX CRM_AsyncOperation_CleanupCompleted
    ON [dbo].[AsyncOperationBase] ([StatusCode],[StateCode],[OperationType])
    GO

    declare @DeleteRowCount int
    Select @DeleteRowCount = 2000
    declare @DeletedAsyncRowsTable table (AsyncOperationId uniqueidentifier not null primary key)
    declare @continue int, @rowCount int
    select @continue = 1
    while (@continue = 1)
    begin      
    begin tran      
    insert into @DeletedAsyncRowsTable(AsyncOperationId)
          Select top (@DeleteRowCount) AsyncOperationId from AsyncOperationBase
          where OperationType in (1, 9, 12, 25, 27, 10) AND StateCode = 3 AND StatusCode in (30, 32)     
           Select @rowCount = 0
          Select @rowCount = count(*) from @DeletedAsyncRowsTable
          select @continue = case when @rowCount <= 0 then 0 else 1 end      
            if (@continue = 1)        begin
                delete WorkflowLogBase from WorkflowLogBase W, @DeletedAsyncRowsTable d
                where W.AsyncOperationId = d.AsyncOperationId             
     delete BulkDeleteFailureBase From BulkDeleteFailureBase B, @DeletedAsyncRowsTable d
                where B.AsyncOperationId = d.AsyncOperationId
     delete WorkflowWaitSubscriptionBase from WorkflowWaitSubscriptionBase WS, @DeletedAsyncRowsTable d
     where WS.AsyncOperationId = d.AsyncOperationID 
                delete AsyncOperationBase From AsyncOperationBase A, @DeletedAsyncRowsTable d
                where A.AsyncOperationId = d.AsyncOperationId             
                delete @DeletedAsyncRowsTable      
    end       
    commit
    end
    --Drop the Index on AsyncOperationBase
    DROP INDEX AsyncOperationBase.CRM_AsyncOperation_CleanupCompleted"
 
    $jobstep.OnSuccessAction = [Microsoft.SqlServer.Management.SMO.Agent.StepCompletionAction]::GoToNextStep
    $jobstep.OnFailAction = [Microsoft.SqlServer.Management.SMO.Agent.StepCompletionAction]::QuitWithFailure
 
    # Create the job step on the instance of SQL Agent.
    $jobstep.Create()

    $jobstep = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Agent.JobStep -argumentlist $job, "Update Statistics"

    $jobstep.Command = "UPDATE STATISTICS [dbo].[AsyncOperationBase] WITH FULLSCAN
    UPDATE STATISTICS [dbo].[DuplicateRecordBase] WITH FULLSCAN
    UPDATE STATISTICS [dbo].[BulkDeleteOperationBase] WITH FULLSCAN
    UPDATE STATISTICS [dbo].[WorkflowLogBase] WITH FULLSCAN
    UPDATE STATISTICS [dbo].[WorkflowWaitSubscriptionBase] WITH FULLSCAN"

    $jobstep.OnSuccessAction = [Microsoft.SqlServer.Management.SMO.Agent.StepCompletionAction]::QuitWithSuccess
    $jobstep.OnFailAction = [Microsoft.SqlServer.Management.SMO.Agent.StepCompletionAction]::QuitWithFailure
 
    # Create the job step on the instance of SQL Agent.
    $jobstep.Create()
 
    # Define a JobSchedule object variable by supplying the parent job and name arguments in the constructor.
    $jobsch =  New-Object -TypeName Microsoft.SqlServer.Management.SMO.Agent.JobSchedule -argumentlist $job, "Monthly_Job_Schedule"
 
    # Set properties to define the schedule frequency, and duration.
    $jobsch.FrequencyTypes = [Microsoft.SqlServer.Management.SMO.Agent.FrequencyTypes]::MonthlyRelative
    $jobsch.FrequencyRelativeIntervals = [Microsoft.SqlServer.Management.SMO.Agent.FrequencyRelativeIntervals]::Last
    $jobsch.FrequencyInterval = [Microsoft.SqlServer.Management.SMO.Agent.MonthlyRelativeWeekDays]::EveryDay
    $jobsch.FrequencyRecurrenceFactor = 1
    $jobsch.ActiveStartDate = Get-Date
    $starttime = New-Object -TypeName TimeSpan -ArgumentList 23, 30, 0
    $jobsch.ActiveStartTimeOfDay = $starttime
 
    # Create the job schedule on the instance of SQL Agent.
    $jobsch.Create();
    Write-Host ("Job `'{0}`' successfully created..." -f $JobName)

    }
#endregion

#region removeAutoLogon
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name DefaultUserName
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name DefaultPassword
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoAdminLogon
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name ForceAutoLogon
#endregion

#region TheEnd
#Clear-Host
Write-Host ''
Write-Host '      .                             ..              .x+=:.         ..                                              ..  ' -ForegroundColor Green
Write-Host '  .x88888x.                   . uW8"       .8u     z`    ^%      dF                                               888B.' -ForegroundColor Green
Write-Host ' :8**888888X.  :>        u.   `t888       m888R-      .   <k    `88bu.             u.      u.    u.              48888E' -ForegroundColor Green
Write-Host ' f    `888888x./   ...ue888b   8888   .    98P      .@8Ned8"    `*88888bu    ...ue888b   x@88k u@88c.      .u    `8888Β΄' -ForegroundColor Green
Write-Host '`       `*88888~   888R Y888r  9888.z88N   ^8     .@^%8888"       ^"*8888N   888R Y888r ^"8888""8888"   ud8888.   Y88F ' -ForegroundColor Green
Write-Host ' \.    .  `?)X.    888R I888>  9888  888E  J"    x88:  `)8b.     beWE "888L  888R I888>   8888  888R  :888`8888.  `88  ' -ForegroundColor Green
Write-Host '  `~=-^   X88> ~   888R I888>  9888  888E +"     8888N=*8888     888E  888E  888R I888>   8888  888R  d888 `88%"   8F  ' -ForegroundColor Green
Write-Host '         X8888  ~  888R I888>  9888  888E         %8"    R88     888E  888E  888R I888>   8888  888R  8888.+"      4   ' -ForegroundColor Green
Write-Host '         488888   u8888cJ888   9888  888E          @8Wou 9%      888E  888F u8888cJ888    8888  888R  8888L        .   ' -ForegroundColor Green
Write-Host ' .xx.     88888X   "*888*P"   .8888  888"        .888888P`      .888N..888   "*888*P"    "*88*" 8888" `8888c. .+  u8N. ' -ForegroundColor Green
Write-Host '`*8888.   `88888>    `Y"       `%888*%"          `   ^"F         `"888*""      `Y"         ""   `Y"    "88888%   "*88% ' -ForegroundColor Green
Write-Host '  88888    `8888>                 "`                                ""                                   "YP`      ""  ' -ForegroundColor Green
Write-Host '  `8888>    `888                                                                                                       ' -ForegroundColor Green
Write-Host '   "8888     8%                                                                                                        ' -ForegroundColor Green
Write-Host '    `"888x:-"                                                                                                          ' -ForegroundColor Green
Write-Host '                                      Now go configure the CRM Email Router πŸ™‚                                         ' -ForegroundColor Cyan
Read-Host
#endregion
}

Web Server security – SSL/TLS

Following the recent attention from the Heartbleed vulnerability, it might be a good idea to have a look at your general SSL/TLS configuration. Being unable to write something more accurate I’ve only supplied to links which details out SSL/TLS versions and support on the different Windows O/S and a free SSL testing tool.
Which protocol is used depends on the server/client negotiated compatibility level. It will, by default use highest possible. – However exploiters will always use lowest possible πŸ™‚

Support for SSL/TLS protocols on Windows
http://blogs.msdn.com/b/kaushal/archive/2011/10/02/support-for-ssl-tls-protocols-on-windows.aspx

SSL Test tool
https://www.ssllabs.com/ssltest/

CRM – Performance – AsyncOperationBase Table

Quite a while back had some issues upgrading a CRM instance. It turned out the amount of ASync operations during the lifetime of the CRM application had generated quite a lot of entries. (millions). For some reason I had a hard time find the root cause of this, which turned out to be a lot more general than just relating to an upgrade. I rarely see CRM installations where a clean up job is created, so most CRM installations will actually suffer from large amount of async operations stored.

Have a look at yourΒ AsyncOperationBase table and see how many entries are there. You might be surprised.

Below are the SQL lines I ran/set up as a maintenance plan in order to reduce the number. Depending on the number of records, it might be necessary to run the scripts a few times.

Code is from referenced KB article:Β http://support.microsoft.com/kb/968520/en-us


IF EXISTS (SELECT name from sys.indexes
                  WHERE name = N'CRM_AsyncOperation_CleanupCompleted')
      DROP Index AsyncOperationBase.CRM_AsyncOperation_CleanupCompleted
GO
CREATE NONCLUSTERED INDEX CRM_AsyncOperation_CleanupCompleted
ON [dbo].[AsyncOperationBase] ([StatusCode],[StateCode],[OperationType])
GO

while(1=1)
begin
 declare @DeleteRowCount int = 10000
 declare @rowsAffected int
 declare @DeletedAsyncRowsTable table (AsyncOperationId uniqueidentifier not null primary key)
 insert into @DeletedAsyncRowsTable(AsyncOperationId)
 Select top (@DeleteRowCount) AsyncOperationId from AsyncOperationBase
 where 
  OperationType in (1, 9, 12, 25, 27, 10) 
  AND StateCode = 3 
  AND StatusCode in (30, 32)
  /*AND CompletedOn <= DATEADD(mm, -1, GETDATE())*/
 
 select @rowsAffected = @@rowcount 
 delete poa from PrincipalObjectAccess poa 
   join WorkflowLogBase wlb on
    poa.ObjectId = wlb.WorkflowLogId
   join @DeletedAsyncRowsTable dart on
    wlb.AsyncOperationId = dart.AsyncOperationId
 delete WorkflowLogBase from WorkflowLogBase W, @DeletedAsyncRowsTable d
 where 
  W.AsyncOperationId = d.AsyncOperationId             
 delete BulkDeleteFailureBase From BulkDeleteFailureBase B, @DeletedAsyncRowsTable d
 where 
  B.AsyncOperationId = d.AsyncOperationId
 delete WorkflowWaitSubscriptionBase from WorkflowWaitSubscriptionBase WS, @DeletedAsyncRowsTable d
 where 
  WS.AsyncOperationId = d.AsyncOperationID 
 delete AsyncOperationBase From AsyncOperationBase A, @DeletedAsyncRowsTable d
 where 
  A.AsyncOperationId = d.AsyncOperationId
 /*If not calling from a SQL job, use the WAITFOR DELAY*/
 if(@DeleteRowCount > @rowsAffected)
  return
 else
  WAITFOR DELAY '00:00:02.000'
end

EDIT: 26-February-2015;
1: Support article code was updated for a more smooth execution.
2: Added required retention time on records of 1 month;

SOA – a perspective of the handshake

Title: SOA – a perspective of the handshake
Audience: Developers, Architects, Organizations, Business Consultants

In Scandinavia the handshake is commonly used in two situations. First as a formal greeting and secondly when a deal is struck. In order to understand the usage of a handshake in regards to Service Oriented Architecture(SOA), focus on the second one – When a deal is struck.
When two sides agrees, a set of terms and a contract is usually crafted to define the exact contents of this arrangement. Who delivers what, where and when. The first step of SOA is understanding this principle of who delivers what, where and when.
Some examples of the who, what, where and when are outlined in the below.

Who; an organization, a person, a service provider, a company.
What; version information in xml, system status from a web service, a physical package in the dimension of 20×20, 1000 rows of analyzed data.
Where; WWW, central database, reception.
When; hourly, daily, every 10th second, on demand.
Please note, that the above examples stretches beyond the normal defining of SOA, which is usually limited to software designs.

What makes this interesting is that this approach will save you time and despair when things are just not working together. Things are often not working together because they were not thought or designed with the other side in mind, this is completely normal. However having defined the four w’s, makes it a lot easier to integrate into these alien areas and comprehending them into a familiar context.

Lets bring back the handshake again. The term handshake has been used commonly within the service industries, were you deliver a service to another person. That can be either a customer or a colleague, make sure that you hand over your work properly, and insure the handshake. That is the exact same which is fundamental in SOA architecture, make sure you have a handshake between your services.

So fundamental thoughts in place, lets fold out a bit. As previously mentioned, SOA is primarily used in designing software applications. Loosely coupled, unassociated functions are very widely used terms in conjunction with SOA. As right as this might be, we should look at this much wider, open the possibilities and expand the perspective. Services exists in much wider terms outside the software world. Whenever a department interacts with another, they are performing a service, handing over processed requests, to other departments who again process these requests until they are handed over to some other company, which completes the service cycle within the first company. Looking at these service cycles using the SOA perspective, should aid in defining them and thereby improving them. Making it easier to integrate, to expand and reduce total service process cost.
So summarizing the above, service oriented architecture is an organizational direction, much more than a software architecture design pattern. Thinking your organization in services just makes it a lot easier to implement IT systems as well πŸ™‚

SOA – a perspective of the handshake (located on Google drive)