Mads Hjort Larsen

Certificate Expiry – All Domain Servers – Powershell

This is a most lovely little piece of powershell.
“This script connects to the DC, gets a list of machines with the β€œOperatingSystem” value set to β€œ*Server*”, if it can succesfully ping the machine(s), it will return a list of certificates that expire after the date specified and compile a CSV file of the results.”

Do check it out and give probs to the man producing the most awesome powershell and now finally with his own blog πŸ™‚

Certificate Expiry

Windows 10 – Privacy settings – Automated

There has been quite some interest in the general privacy settings of the different applications and systems that we use in our daily life. Windows 10 is no exception to this, as Microsoft now by default, has chosen for you that you are very interested in sharing your traffic information with them. You can of course disable this tracking, however it is not always obvious how to do so. Once again my friend Mads Hjort Larsen has created a script to automate these privacy settings for you.

Have a look at the section “What should be changed” and make sure you change accordingly. ($false or $true)

Also note that once again WordPress has decided to mess with the formatting/syntax highlighting of the script. That is also why the third link in the comment section is surround by apostrophes.

    This script automates the changing of a lot of settings that are otherwise hard or tedious to change.
    This script consolidates a lot of Windows registry changes and changes to other operating system settings, 
    that affect the appearance and functionality of the Windows 10 operating system, with the intent of making
    it more userfriendly and increasing the privacy.
    Created by Mads Hjort Larsen

# What should be changed:
[bool]$privacySettings = $true  # change the settings related to privacy
[bool]$removeSoftware  = $false # remove Cortana, OneDrive and Metro Apps
[bool]$windowsUpdates  = $false # setup updates to NOT automatically reboot or download via P2P
[bool]$UIsettings      = $false # make win 10 look more like win 7/8/8.1

########### DO NOT EDIT BELOW THIS LINE #################################

If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
    Write-Warning "You do not have the Admin rights neccessary to run this script!`nPlease re-run this script as an Administrator!"

New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

if($privacySettings -eq $true){
# Disable Cortana
New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search' -Name AllowCortana -PropertyType DWORD -Value 0 -ErrorAction SilentlyContinue

# Disable Data Logging Services
Get-Service diagtrack,dmwappushservice,RetailDemo | Stop-Service -PassThru | Set-Service -StartupType disabled
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name AllowTelemetry -PropertyType DWORD -Value 0 -Force
New-ItemProperty -Path HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name AllowTelemetry -PropertyType DWORD -Value 0 -Force

# Disable relevant scheduled tasks
schtasks /change /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\Consolidator" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /DISABLE

# Erase the contents of AutoLogger-Diagtrack-Listener.etl
echo "" > C:\ProgramData\Microsoft\Diagnosis\ETLLogs\AutoLogger\AutoLogger-Diagtrack-Listener.etl

# Edit Hosts File
$hostsPath = "$env:windir\System32\drivers\etc\hosts"
$hosts = get-content $hostsPath
[array]$urls = @(

foreach($url in $urls){
    $hostLine = ' '+$url

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

# WiFi Sense: HotSpot Sharing: Disable
Set-ItemProperty -Path HKLM:\Software\Microsoft\PolicyManager\default\WiFi\AllowWiFiHotSpotReporting -Name value -Type DWORD -Value 0

# WiFi Sense: Shared HotSpot Auto-Connect: Disable
Set-ItemProperty -Path HKLM:\Software\Microsoft\PolicyManager\default\WiFi\AllowAutoConnectToWiFiSenseHotspots -Name value -Type DWORD -Value 0

# Start Menu: Disable Bing Search Results
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name BingSearchEnabled -Type DWORD -Value 0

# Privacy: Disable Edge suggestions
Set-ItemProperty -Path HKL:\SOFTWARE\Policies\Microsoft\MicrosoftEdge\SearchScopes -Name ShowSearchSuggestionsGlobal -Type DWORD -Value 0

# Privacy: Let apps use my advertising ID: Disable
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo -Name Enabled -Type DWORD -Value 0

# Privacy: SmartScreen Filter for Store Apps: Disable
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost -Name EnableWebContentEvaluation -Type DWORD -Value 0

if($removeSoftware -eq $true){
# Uninstall OneDrive
Stop-Process -ProcessName *OneDrive*
if(Test-Path "$env:windir\SysWOW64\OneDriveSetup.exe" -eq $true){Start-Process "$env:windir\SysWOW64\OneDriveSetup.exe" -ArgumentList '/uninstall' -Wait}
else{Start-Process "$env:windir\System32\OneDriveSetup.exe" -ArgumentList '/uninstall' -Wait}
Stop-Process -ProcessName *OneDrive* 

Remove-Item "%USERPROFILE%\OneDrive" -Recurse -Force
Remove-Item "C:\OneDriveTemp" -Recurse -Force
Remove-Item "%LOCALAPPDATA%\Microsoft\OneDrive" -Recurse -Force
Remove-Item "%PROGRAMDATA%\Microsoft OneDrive" -Recurse -Force

# Remove Cortana
Get-Process -Name *cortana* | Stop-Process
Get-AppxPackage -AllUsers | ? {$_.Name -match 'Cortana'} | Remove-AppxPackage -ErrorAction SilentlyContinue

#region Windows 10 Metro App Removals
# Be gone, heathen!
Get-AppxPackage | Remove-AppxPackage

# Bing Weather, News, Sports, and Finance (Money):
Get-AppxPackage Microsoft.BingWeather | Remove-AppxPackage
Get-AppxPackage Microsoft.BingNews | Remove-AppxPackage
Get-AppxPackage Microsoft.BingSports | Remove-AppxPackage
Get-AppxPackage Microsoft.BingFinance | Remove-AppxPackage

# Xbox:
Get-AppxPackage Microsoft.XboxApp | Remove-AppxPackage

# Windows Phone Companion
Get-AppxPackage Microsoft.WindowsPhone | Remove-AppxPackage

# Solitaire Collection
Get-AppxPackage Microsoft.MicrosoftSolitaireCollection | Remove-AppxPackage

# People
Get-AppxPackage Microsoft.People | Remove-AppxPackage

# Groove Music
Get-AppxPackage Microsoft.ZuneMusic | Remove-AppxPackage

# Movies & TV
Get-AppxPackage Microsoft.ZuneVideo | Remove-AppxPackage

# OneNote
Get-AppxPackage Microsoft.Office.OneNote | Remove-AppxPackage

# Photos
Get-AppxPackage Microsoft.Windows.Photos | Remove-AppxPackage

# Sound Recorder
Get-AppxPackage Microsoft.WindowsSoundRecorder | Remove-AppxPackage

# Mail & Calendar
Get-AppxPackage microsoft.windowscommunicationsapps | Remove-AppxPackage

# Skype (Metro version)
Get-AppxPackage Microsoft.SkypeApp | Remove-AppxPackage


if($windowsUpdates -eq $true){
# Change Windows Updates to "Notify to schedule restart"
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Name AUOptions -Type DWORD -Value 4

New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows -Name WindowsUpdate
New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate -Name AU 
New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name AUOptions -Type DWORD -Value 4 -Force

# Disable P2P Update downloads outside of local network
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config -Name DODownloadMode -Type DWORD -Value 1
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization -Name SystemSettingsDownloadMode -Type DWORD -Value 3

# To disable P2P update downloads completely:
#Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config -Name DODownloadMode -Type DWORD -Value 0

if($UIsettings -eq $true){
# Change Explorer home screen back to "This PC"
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name LaunchTo -Type DWORD -Value 1

# Disable Quick Access: Recent Files
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer -Name ShowRecent -Type DWORD -Value 0

# Disable Quick Access: Frequent Folders
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer -Name ShowFrequent -Type DWORD -Value 0

# Disable the Lock Screen (the one before password prompt - to prevent dropping the first character)
If (-Not (Test-Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization)) {New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows -Name Personalization | Out-Null}
Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization -Name NoLockScreen -Type DWORD -Value 1

# Use the Windows 7-8.1 Style Volume Mixer
If (-Not (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MTCUVC")) {New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name MTCUVC | Out-Null}
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MTCUVC" -Name EnableMtcUvc -Type DWORD -Value 0

# Remove folders from MyPC
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{1CF1260C-4DD0-4ebb-811F-33C572699FDE}" -Force # Music
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{374DE290-123F-4565-9164-39C4925E467B}" -Force # Downloads
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{3ADD1653-EB32-4cb0-BBD7-DFA0ABB5ACCA}" -Force # Pictures
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{A0953C92-50DC-43bf-BE83-3742FED03C9C}" -Force # Videos
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{A8CDFF1C-4878-43be-B5FD-F8091C1C60D0}" -Force # Documents

Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{088e3905-0323-4b02-9826-5d99428e115f}" -Force # Downloads 
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{24ad3ad4-a569-4530-98e1-ab02f9417aa8}" -Force # Pictures
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{3dfdf296-dbec-4fb4-81d1-6a3438bcf4de}" -Force # Music
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}" -Force # Desktop
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{d3162b92-9365-467a-956b-92703aca08af}" -Force # Documents
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{f86fa3ab-70d2-4fc7-9c99-fcbf05467f3a}" -Force # Videos
# Remove OneDrive from the Explorer Side Panel.
Remove-Item "HKCR:\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" -Force -Recurse
Remove-Item "HKCR:\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" -Force -Recurse

# Remove Compressed file/folder
Remove-Item "HKCR:\CABFolder\CLSID" -Force -Recurse
Remove-Item "HKCR:\CompressedFolder\CLSID" -Force -Recurse
Remove-Item "HKCR:\SystemFileAssociations\.cab\CLSID" -Force -Recurse
Remove-Item "HKCR:\SystemFileAssociations\.zip\CLSID" -Force -Recurse

# Explorer: Show all folders
New-ItemProperty -path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneShowAllFolders -PropertyType DWORD -Value 1

# Explorer: Show file extensions
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name HideFileExt -PropertyType DWORD -Value 0

# Explorer: Show hidden files
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name Hidden -PropertyType DWORD -Value 1

# Taskbar: Show all icons in taskbar
New-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Explorer -Name EnableAutoTray -PropertyType DWORD -Value 0
New-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name NavPaneShowAllFolders -PropertyType DWORD -Value 1

# Taskbar: Hide Task View Button
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name ShowTaskViewButton -PropertyType DWORD -Value 0

# Taskbar: Hide Search
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name SearchboxTaskbarMode -PropertyType DWORD -Value 0

# Bring back old Windows Update control panel app
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX -Name IsConvergedUpdateStackEnabled -Value 0 -Force
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings -Name UxOption -Value 0 -Force

# Titlebar: Enable accent colors
Copy-Item -Path $env:windir\Resources\Themes\aero -Recurse -Destination $env:windir\Resources\Themes\windows -Force -ErrorAction SilentlyContinue
Get-ChildItem -Path $env:windir\Resources\Themes\windows -Filter "aero.msstyles*" -Recurse | Rename-Item -NewName {$ -replace 'aero','windows' }
(Get-Content $env:windir\Resources\Themes\aero.theme).Replace('Path=%ResourceDir%\Themes\Aero\Aero.msstyles', 'Path=%ResourceDir%\Themes\windows\windows.msstyles') | Set-Content $env:TEMP\windows.theme
Start-Process $env:TEMP\windows.theme -Wait
(New-Object -comObject Shell.Application).Windows() | where-object {$_.LocationName -eq "Personalization"} | foreach-object {$_.quit()}
Remove-Item $env:TEMP\windows.theme

# Finishing touches
Write-Host "Please make sure you do the following as well:`nSelect Never in the first box, and Basic in the second box"
start ms-settings:privacy-feedback

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.

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.

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>'

$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.

$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>'

$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 = ""
$RSemailAddress = ""

$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

$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 = '' # IP address and subnet mask, such as 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 = '' # 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
$webapplicationrootdomain = '' # 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 
$discoveryrootdomain = '' # 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 for more info.

   $Server = ''
   $Router = ''
      $SRS = ''
   $MUIENU = ''
   $MUIDAN = ''

# 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)

if (!(Test-Path placeholder.tmp)) {
Add-Clock | Out-Null
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

#region CreateUsers
[ScriptBlock]$ScriptBlock = { 

    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

#region LogonAsService
function Grant-LogOnAsService{
    [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"
        $newSids = $sids + $sidstring
        Write-Host "`nNew Sids: " -NoNewline
        $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
        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

#region Add-DomainUserToLocalGroup
function Add-DomainUserToLocalGroup { 
    $de = [ADSI]"WinNT://$computer/$Group,group" 

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

# 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 = {
    Import-Module -Name SQLPS -DisableNameChecking
    Add-Type -AssemblyName "Microsoft.SqlServer.Smo, Version=, 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
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")

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

$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 = {
    $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 = {
    $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

#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

#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"

Write-Host "`nRebooting..."
Add-Clock | Out-Null
#region CRMinstall
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

#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

#region EmailRouter
Write-Host "Downloading Microsoft Exchange Server MAPI Client and Collaboration Data Objects 1.2.1"
$source = ''
$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.
$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

#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)
    $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]::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)
        $downloadedBytes += $count
    Write-Host ''
    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

#region MaintenancePlan 
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"
            Write-Host "Waiting for Service to Start." -NoNewline
            While($Service.Status -ieq "StartPending"){
                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"
            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.
        $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)
                    Write-Host ("Dropping Job: `'{0}`'." -f $JobName)
                } else {
    } 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.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
    CREATE NONCLUSTERED INDEX CRM_AsyncOperation_CleanupCompleted
    ON [dbo].[AsyncOperationBase] ([StatusCode],[StateCode],[OperationType])

    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 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      
    --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 = 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].[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.
    # 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.
    Write-Host ("Job `'{0}`' successfully created..." -f $JobName)


#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

#region TheEnd
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

Workflow Manager – Scripted Installation

Allrighty, I was sick and tired of NOT being able to install my Workflow Manager without the Web-platform Installer installation. Secondly I was tired of not being able to install Workflow Manager using my loved one, Powershell. So I calledΒ upon my good friendΒ Mads Hjort Larsen and we started to work on it.
The script below will make use of offline msi files for the installation. So make sure you grab those before you run the script. They should be located in the same folder as the script before you run it.

The script

Set-Location (Split-Path -Parent -Path $MyInvocation.MyCommand.Definition)

#region Variables
$SQLserver = '<SQL SERVER>' # Name of the database server.
$DBprefix = '<Database Pre-fix>' # All databases will be prefixed with this string.

$SBRunAsAccount = '<Service Bus service account>' # The account under which the service runs. This account must be a domain account.
$SBRunAsPasswordString = '<Service Bus service Account passwprd>' # Password for RunAs account (in cleartext).
$SBAdminGroup = '<Service Bus Administrator group>' # The admin group for Service Bus. (Default "BUILTIN\Administrators")
$SBCertificateAutoGenerationKey = '<Certificate passphrase>' # This passphrase is required for certificate auto generation. This parameter is mandatory if you want certificates to be auto generated.
$TcpPort = <Service Bus port> # The port that the Service Bus for Windows Server uses for TCP. (Default 9354) 
$MessageBrokerPort = <Message Broker port> 9356 # The port that the Service Bus for Windows Server uses for MessageBroker communication. (Default 9356)
$InternalPortRangeStart = <Port Range> # The start of the port range that Service Bus for Windows Server uses for internal communication purposes. (Default 9000) 

$WFRunAsAccount = '<Service Bus service account>' # The account under which the service will be running. This account must be a domain account.
$WFRunAsPasswordString = '<Service Bus service account password>' # Password for RunAs account (in cleartext).
$WFAdminGroup = '<Service Bus Administrator group>' # The set of users who are considered workflow administrators. (Default BUILTIN\Administrators)
$WFCertificateAutoGenerationKey = '<Certificate Passphrase>' # This passphrase is required for certificate auto generation. This is a mandatory parameter if you want certificates to be auto generated.
$HttpsPort = <Workflow HTTPS Port> # The port that will be used by the workflow for HTTPS communication. (Default 12290)
$HttpPort = <Workflow HTTP Port> # The port that will be used by the workflow for HTTP communication. (Default 12291)

$SBNamespace = '<Service Bus Namespace>' # Specifies the name for the new Service Bus for Windows Server service namespace.
$AddressingScheme = 'Path' # Specifies the addressing scheme used in the service namespace. The possible values for this parameter are Path (default value) and DNSRegistered. If the value DNSRegistered is specified, the -DnsEntry parameter is required.
$ManageUsers = '<Workflow Namespace Administrator01>','<Workflow Namespace Administrator02>' # Specifies user or group names that will be managers of the service namespace.

$WF  = "WindowsFabric.msi"
$SB  = "Service_Bus.msi"
$WMC = "WorkflowManagerClient_x64.msi"
$WM  = "Workflow_Manager.msi"

## Can't touch this... or anything below this line ;P

$Sleep = 90 # Sleep duration is set to 90 seconds because that what all the cool kids online are doing.

    "IACCEPTEULA=yes" is required for quiet install of Windows Fabric, 
    "WEBPI=1" is required for installation of Service Bus and Workflow Manager outside of the Web Platform Installer, 
    "/QUIET" = Quiet mode, no user interaction, 
    "/NORESTART" = Do not restart after the installation is complete.

$fileNotFound = $null

$files = $WF,$SB,$WMC,$WM
foreach ($file in $files) {
    if (Test-Path -Path $file){
        Write-Host "βœ“ $file" -ForegroundColor Green
    }else{Write-Host "X $file" -ForegroundColor Red;$fileNotFound = $true}
}if ($fileNotFound -eq $true){Exit}
foreach ($file in $files) {
    Write-Host "Installing $file... " -NoNewline
    Start-Process $file -ArgumentList $ArgumentList -Wait
    Write-Host "Done"

[Environment]::SetEnvironmentVariable("PSModulePath", [Environment]::GetEnvironmentVariable("PSModulePath","Machine"))
Import-Module WorkflowManager

#region StringBuilding
# The strings are created like this to make it easier to put them on different SQL servers
function BuildString ($type, $dbName, $SQLserver) {
    New-Variable -Name $type'DBConnectionStringDataSource' -Value $SQLserver
    New-Variable -Name $type'DBConnectionStringInitialCatalog' -Value $DBprefix$dbName
    New-Variable -Name $type'DBConnectionString' -Scope Script -Value ("Data Source="+(Get-Variable -Name $type'DBConnectionStringDataSource' -ValueOnly)+";Initial Catalog="+(Get-Variable -Name $type'DBConnectionStringInitialCatalog' -ValueOnly)+";Integrated Security=True;Encrypt=False") -Force

# Example ConnectionString: Data Source=SQL01;Initial Catalog=Udv_service_SBManagement;Integrated Security=True;Encrypt=False
BuildString SBFarm SBManagement $SQLserver
BuildString GateWay SBGateway $SQLserver
BuildString MessageContainer SBMessageContainer $SQLserver
BuildString WFFarm WFManagement $SQLserver
BuildString Instance WFInstanceManagement $SQLserver
BuildString Resource WFResourceManagement $SQLserver

$SBCertificateAutoGenerationKey = ConvertTo-SecureString -AsPlainText -Force -String $SBCertificateAutoGenerationKey -Verbose
New-SBFarm -SBFarmDBConnectionString $SBFarmDBConnectionString -InternalPortRangeStart $InternalPortRangeStart -TcpPort $TcpPort -MessageBrokerPort $MessageBrokerPort -RunAsAccount $SBRunAsAccount -AdminGroup $SBAdminGroup -GatewayDBConnectionString $GatewayDBConnectionString -CertificateAutoGenerationKey $SBCertificateAutoGenerationKey -MessageContainerDBConnectionString $MessageContainerDBConnectionString -Verbose
New-WFFarm -WFFarmDBConnectionString $WFFarmDBConnectionString -RunAsAccount $WFRunAsAccount -AdminGroup $WFAdminGroup -HttpsPort $HttpsPort -HttpPort $HttpPort -InstanceDBConnectionString $InstanceDBConnectionString -ResourceDBConnectionString $ResourceDBConnectionString -CertificateAutoGenerationKey $SBCertificateAutoGenerationKey -Verbose

$SBRunAsPassword = ConvertTo-SecureString -AsPlainText -Force -String $SBRunAsPasswordString -Verbose
Add-SBHost -SBFarmDBConnectionString $SBFarmDBConnectionString -RunAsPassword $SBRunAsPassword -EnableFirewallRules $true -CertificateAutoGenerationKey $SBCertificateAutoGenerationKey -Verbose

Try {
    New-SBNamespace -Name $SBNamespace -AddressingScheme $AddressingScheme -ManageUsers $ManageUsers -Verbose
    for ($i=$Sleep; $i -gt 1; $i--) {
        Write-Progress -Activity "Creating new SB Namespace" -Status "Sleeping" -SecondsRemaining $i
        Start-Sleep 1
Catch [system.InvalidOperationException]{}

$SBClientConfiguration = Get-SBClientConfiguration -Namespaces $SBNamespace -Verbose
$WFRunAsPassword = ConvertTo-SecureString -AsPlainText -Force -String $WFRunAsPasswordString -Verbose
Add-WFHost -WFFarmDBConnectionString $WFFarmDBConnectionString -RunAsPassword $WFRunAsPassword -EnableFirewallRules $true -SBClientConfiguration $SBClientConfiguration -CertificateAutoGenerationKey (ConvertTo-SecureString $WFCertificateAutoGenerationKey -AsPlainText -Force) -Verbose

(The script is currently using a self-signed certificate.)

How to get MSI files
WebPICMD.exe /offline /products:”WindowsFabric,ServiceBus_1_1,WorkflowManager” /Path:c:\temp\

WebPICMD.exe /offline /products:WindowsFabric /Path:c:\temp\

WebPICMD.exe /offline /Products:ServiceBus_1_1 /Path:c:\temp\

WebPICMD.exe /offline /Products:WorkflowManager /Path:c:\temp\

WebPICMD.exe /offline /Products:WorkflowManager /Path:c:\temp\

Updated 17-June-2015: Incorporated Martin Sandersens comment into the script, removing the need for customized msi files.
Updated 07-August-2015: Updated script for fewer lines of code.

SharePoint Web Services – HTTPS – Certificate

​During one of the ongoing projects I am associated with, we had a peculiar problem. In a multiple server farm setup for https, the service application Uri are also created with https URL’s. Accessing a site on https requires a valid certificate trusted by the requester or disabling the certificate check. There was some .Net code that disables this check; however, this affected the entire application pool until next recycle, so we did not want to use that.

We found out that the server address in these end points are created from the value SharePoint has stored for the server. (Get-SPServer).

For the entire solution we had been using a wildcard certificate, as it offers more flexibility and ease of implementation. However as the server was not FQDN, the wildcard certificate would not work. So what options did we have?

1: Use the Rename-SPServer and change the internal server in SharePoint to the FQDN, and thereby use the wildcard certificate. We tried this, but saw quite a few effects from this change. Therefore, we did not choose to proceed with this. This could properly be a valid approach, if the SharePoint internal names were configured initially using FQDN and not NetBIOS name.

2: Create single server certificates for each server in the farm, as well as adding each certificate to each server. This approach was not chosen due to the amount of administration involved initially and if adding servers later.

3: One of my colleagues came up with the brilliant plan to use the SharePoint internal Root certificate, as this is already trusted by all SharePoint servers, there is no need for additional certificates. Another colleague of mine wrote a little script, which pulls out this SharePoint Root certificate from the Certificate Store, adds it to the IIS, and binds it to the SharePoint Web Services site.

Updated: Check for port used as well as binding on static certificate name, rather than array.

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

Exports the currently used SharePoint Services certificate and uses it to create an SSL binding for SharePoint Web Services
This script lists all the certificates in the local SharePoint store, selects the one which matches
"SharePoint Services" and then calls certutil to import the certificate and key.
It then gets the current port of the webbinding named "SharePoint Web Services", removes the
webbinding and re-creates it with an sslbinding using the certificate.
File Name : export-spCert-import-in-IIS.ps1
Author : Mads Hjort Larsen -
Certificate "CN=SharePoint Services, OU=SharePoint, O=Microsoft, C=US" added

CertUtil: -importPFX command completed successfully.

IP Address Β  Β  Β  Port Β  Β  Β  Β Store Β  Β Sites
---------- Β  Β  Β  Β  Β  Β  ---- Β  Β  Β  Β  Β  ----- Β  Β  Β ----- Β  Β  Β  Β  Β  Β  Β 32844 Β  Β My Β  Β  Β  SharePoint Web Services


$cert = dir cert:\localmachine\sharepoint | Where-Object {$_.Subject -match "SharePoint Services"}
$type = [System.Security.Cryptography.X509Certificates.X509ContentType]::pfx
$pass = "password"
$certPath = (Split-Path $MyInvocation.MyCommand.Path)+"file.pfx"
$bytes = $cert.export($type, $pass)
[System.IO.File]::WriteAllBytes($certPath, $bytes)

certutil -f -p "$pass" -importpfx "$certPath"

Import-Module WebAdministration
$port = ((Get-WebBinding -Name "SharePoint Web Services" -Protocol https).bindingInformation).Trim("*:")
if((Get-WebBinding -Name "SharePoint Web Services" -IP "*" -Port $port -Protocol https) -ne $null) {Remove-WebBinding -Name "SharePoint Web Services" -IP "*" -Port $port -Protocol https}
New-WebBinding -Name "SharePoint Web Services" -IP "*" -Port $port -Protocol https
$certObj = Get-Item $cert.PSPath
if(Test-Path IIS:\SslBindings.0.0.0!$port){Remove-Item IIS:\SslBindings.0.0.0!$port}
New-Item IIS:\SslBindings.0.0.0!$port -value $certobj
Remove-Module WebAdministration

Windows Update from Specific date.

We had a pretty standard situation, wanted to patch a bunch of servers with all patches up to a specific date. Colleague and friend of mine created the below script which proved extremely helpful.

All credits to the below mentioned people.

NAME: update-installer.ps1
AUTHOR: Mads H. Larsen
Tested on PSversions: 2.0, 3.0

This script checks windows update for applicable updates to the system,
and then downloads and installs them. If you enter a date that is earlier
than todays date, only updates from that date or earlier are installed.

Most of the inner workings of the script is copied from:

I just added some code to compare the dates, check connectivity, show size
and elapsed time and do some formatting.
$runTime = [System.Diagnostics.Stopwatch]::StartNew()
Write-Host "Verifying connectivity, please wait..."
[string] $url = ''
[net.httpWebRequest] $req = [net.webRequest]::create($url)
$req.Method = "HEAD"

Try {[net.httpWebResponse] $res = $req.getResponse()}
Catch {Write-Host "$url is not available. Could not resolve $($url) " -foregroundColor red;return}
if (!($res.StatusCode -ge "200" -and $res.StatusCode -lt "400")) {Write-Host "$($url) is not available. HTTP StatusCode $($res.StatusCode)" -foregroundColor red;return}


function Get-WIAStatusValue($value)
switch -exact ($value)
0 {"NotStarted"}
1 {"InProgress"}
2 {"Succeeded"}
3 {"SucceededWithErrors"}
4 {"Failed"}
5 {"Aborted"}
$sw = New-Object System.Diagnostics.Stopwatch

$number = 0
$totalSize = 0
$totalDownloaded = 0
$needsReboot = $false

$date = Get-Date (Read-Host "Input target date ($((Get-culture).DateTimeFormat.ShortDatePattern))")

$updateSession = New-Object -ComObject Microsoft.Update.Session
$updateSearcher = $updateSession.CreateUpdateSearcher()

Write-Host " - Searching for updates"
$searchResult = $updateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")
$updates = $searchResult.Updates | Where-Object {($_.LastDeploymentChangeTime -le $date)}

Write-Host " - Found [$($updates.count)] updates to download and install"
if($($updates.count) -eq $null) {return}

$updates | Format-Table @{Expression={($_.LastDeploymentChangeTime).ToString(" dd-MM-yyyy ")};Alignment=”center”;Label="dd-MM-yyyy"},@{Expression={$_.KBArticleIDs};Alignment=”center”;Label=" KBID "},@{Expression={$_.MsrcSeverity};Alignment=”center”;Label="Severity "} -AutoSize
$totalSize = ($searchResult.Updates | measure-Object -sum MaxDownloadSize).Sum
Write-Host " ---------------------------------"
Write-Host (" = Total of {0:n2} MB to download`n" -f ($totalSize/1MB))

foreach($update in $updates)
# Add update to collection
Write-Host " [$(Get-Date -Format dd)-$(Get-Date -Format MM)-$(Get-Date -Format yyyy) @ $(Get-Date -Format HH):$(Get-Date -Format mm):$(Get-Date -Format ss)]"
$updatesCollection = New-Object -ComObject Microsoft.Update.UpdateColl
if ( $update.EulaAccepted -eq 0 ) { $update.AcceptEula() }
$updatesCollection.Add($update) | out-null
# Download
$size = $update.MaxDownloadSize/1MB
$size = "{0:N2}" -f $size
$totalDownloaded = $totalDownloaded + $size
Write-Host " + Downloading $number of $($updates.count) - $size MB:" $($update.Title)
$updatesDownloader = $updateSession.CreateUpdateDownloader()
$updatesDownloader.Updates = $updatesCollection
$downloadResult = $updatesDownloader.Download()
$message = " - Download {0}" -f (Get-WIAStatusValue $downloadResult.ResultCode)
if($sw.Elapsed.Seconds -lt 1){Write-Host $message "in less than 1 second"}else{Write-Host $message "in $($sw.Elapsed.Hours)h$($sw.Elapsed.Minutes)m$($sw.Elapsed.Seconds)s"}
# Install
Write-Host " - Installing update"
$updatesInstaller = $updateSession.CreateUpdateInstaller()
$updatesInstaller.Updates = $updatesCollection
$installResult = $updatesInstaller.Install()
$message = " - Install {0}" -f (Get-WIAStatusValue $downloadResult.ResultCode)
if($sw.Elapsed.Seconds -lt 1){Write-Host $message "in less than 1 second"}else{Write-Host $message "in $($sw.Elapsed.Hours)h$($sw.Elapsed.Minutes)m$($sw.Elapsed.Seconds)s"}
$needsReboot = $installResult.rebootRequired
if($needsReboot){Write-Host " One or more updates require a reboot to complete installation" -foregroundcolor "red"}

Write-Host -foregroundColor yellow "`n $totalDownloaded MB of updates downloaded and installed in $($runTime.Elapsed.Hours) hour(s) $($runTime.Elapsed.Minutes) minute(s) $($runTime.Elapsed.Seconds) second(s)."