Install-ADFS_Demo_with_OTP.ps1

#Requires -RunAsAdministrator
#Requires -Version 4.0

<#
     .SYNOPSIS
        Installs ADFS Service with SecureMFA OTP Provider Web Portal on empty Windows Server.
    .DESCRIPTION
        Installs ADFS Service with SecureMFA OTP Provider on a single ADFS server using MS SQLEXPRESS database.
 
        Dependencies:
            * Deployment must be done with service account which will be used for ADFS service. Account must be member or local computer administrators� group and Domain Administrators groups for ADFS service to register successfully.
            * Server is Windows 2019 or 2016 serve and joined to domain.
            * Server has a valid certificate for ADFS Service communication. It must have CN name which is different to computer's FQDN
            * Server has "SqlServer" PS Module installed.
            * Server has a free edition of MSSQL SQLEXPRESS service installed using default Instance Name and Instance ID - SQLEXPRESS
            * Server has Microsoft Framework 4.6.1 and above installed.
  
    .NOTES
        Version: 2.0.0.1
        Author: SecureMfa.com
        Creation Date: 27/07/2020
        Purpose/Change: Release
   
    .EXAMPLE
        C:\PS> Install-ADFS_Demo_with_OTP -adfs_certificate_thumbprint "285394eae4e52bf64d3b6f6c304584f30c3f004f"
 
        This command will install ADFS service with SecureMFA OTP Provider and Web Portal which allows to enrol users on a single ADFS server using MSSQL SQLEXPRESS database.
    
#>


$zipfilepath = (Join-Path -Path $PSScriptRoot -ChildPath SecureMFA_OTP_SPA.zip)
$sqlservermodulepath = "C:\Program Files\WindowsPowerShell\Modules\SqlServer"

#Check if windows events source for application log exist, if not create one.
if ([System.Diagnostics.EventLog]::SourceExists("Secure MFA OTP") -eq $False) {New-EventLog -LogName "Application" -Source "Secure MFA OTP" ; Write-Host "Secure MFA OTP Log Source Created."}


Function Install-ADFS_Demo_with_OTP {
Param
(
    [Parameter(Mandatory=$true)][string]$adfs_certificate_thumbprint,
    [Parameter(Mandatory=$false)][Switch]$Force
)
    
    #Check if ADFS service existi on the system
    if(((Get-Service adfssrv -ErrorAction SilentlyContinue).Status -ne $null) -and (!($Force))) {write-host "ADFS Service is allready running on this server $env:COMPUTERNAME . Please use new Windows build or unistall ADFS deployment before retry." -ForegroundColor Yellow; break}
    
    #Check if ADFS service existi on the system
    if(((Get-Service 'MSSQL$SQLEXPRESS' -ErrorAction SilentlyContinue).Status -ne 'Running') -and (!($Force))) {write-host "MSSQL SQLEXPRESS Edition is not running on a server $env:COMPUTERNAME . Please make sure you deploy a Free editon of MSSQL SQLEXPRESS Edition using default Instance Name and Instance ID: SQLEXPRESS on this computer to continue." -ForegroundColor Yellow; break}   
        
    try
    {
        $Error.Clear()
        if (!(Test-Path $zipfilepath -Type Leaf) ) { throw "$zipfilepath does not exist" ; break}
        if (!(Test-Path $sqlservermodulepath) ) { throw "SqServer module $sqlservermodulepath does not exist please install it from PSGalery: Install-Module sqlserver" ; break}
        if(!(Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $adfs_certificate_thumbprint})) {throw "Certificate $adfs_certificate_thumbprint does not exist in Cert:\LocalMachine\My. Please install certificate first before retry." ; break}

    #Start deployment
        
    #SQL Procedure for WID Database creation
    $SQLProcedureTemplate = @( 
    "USE [master]",
    "GO",
    "/****** Object: Database [SecureMfaOTP] Script Date: 27/01/2020 21:52:40 ******/",
    "CREATE DATABASE [SecureMfaOTP]",
    "CONTAINMENT = NONE",
    "ON PRIMARY",    
    "( NAME = N'SecureMfaOTP', FILENAME = N'C:\SecureMFA\SecureMfaOTP.mdf' , SIZE = 6144KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )",
    "LOG ON", 
    "( NAME = N'SecureMfaOTP_log', FILENAME = N'C:\SecureMFA\SecureMfaOTP_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)",
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET COMPATIBILITY_LEVEL = 120",
    "GO",
    "IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))",
    "begin",
    "EXEC [SecureMfaOTP].[dbo].[sp_fulltext_database] @action = 'enable'",
    "end",
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ANSI_NULL_DEFAULT OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ANSI_NULLS OFF",
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ANSI_PADDING OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ANSI_WARNINGS OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ARITHABORT OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET AUTO_CLOSE OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET AUTO_SHRINK OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET AUTO_UPDATE_STATISTICS ON", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET CURSOR_CLOSE_ON_COMMIT OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET CURSOR_DEFAULT GLOBAL", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET CONCAT_NULL_YIELDS_NULL OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET NUMERIC_ROUNDABORT OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET QUOTED_IDENTIFIER OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET RECURSIVE_TRIGGERS OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET DISABLE_BROKER", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET AUTO_UPDATE_STATISTICS_ASYNC OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET DATE_CORRELATION_OPTIMIZATION OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET TRUSTWORTHY OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET ALLOW_SNAPSHOT_ISOLATION OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET PARAMETERIZATION SIMPLE", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET READ_COMMITTED_SNAPSHOT OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET HONOR_BROKER_PRIORITY OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET RECOVERY SIMPLE", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET MULTI_USER", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET PAGE_VERIFY CHECKSUM",  
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET DB_CHAINING OFF", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET TARGET_RECOVERY_TIME = 0 SECONDS", 
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET DELAYED_DURABILITY = DISABLED", 
    "GO",
    "EXEC sys.sp_db_vardecimal_storage_format N'SecureMfaOTP', N'ON'",
    "GO",
    "ALTER DATABASE [SecureMfaOTP] SET READ_WRITE", 
    "GO",
    "USE [SecureMfaOTP]",
    "GO",
    "CREATE TABLE [dbo].[Secrets](",
    "[upn] [varchar](255) NOT NULL,",
    "[secret] [char](48) NOT NULL,",
    "[logon] [tinyint] NULL,",
    "[lastlogon] [datetime] NULL,",
    "[logoncount] [int] DEFAULT 0 NOT NULL,",
    "[failedlogoncount] [int] DEFAULT 0 NOT NULL,",
    "[failedlastlogon] [datetime] NULL,",
    "[failedcode] [char](6),",
    "[logonip] [varchar](39),",
    "[useragent] [varchar](128),",
    " CONSTRAINT [PK_Secrets] PRIMARY KEY CLUSTERED", 
    "(",
    "[upn] ASC",
    ")WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]",
    ") ON [PRIMARY]",
    "GO",
    "CREATE TABLE [dbo].[UsedCodes](",
    "[upn] [varchar](255) NOT NULL,",
    "[interval] [bigint] NOT NULL",
    ") ON [PRIMARY]",
    "GO"  
    )

    #Start
    $adfs_service_account = "$env:USERDOMAIN\$env:USERNAME"
    do {
    if($inputfailures -eq 0) {Write-Host "Please enter a password for ADFS Service Account $adfs_service_account :"} else {Write-Host "[Retry: $inputfailures] Passwords didn't match for $adfs_service_account Try again:"}
    $pwd1 = Read-Host "Password" -AsSecureString
    $pwd2 = Read-Host "Re-enter Password" -AsSecureString
    $pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd1))
    $pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd2))
    $inputfailures++
    }
    while ($pwd1_text -ne $pwd2_text )
    $fscredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $adfs_service_account,$pwd1

    #Create Temp folder and export SQL procedure
    New-Item -Path "c:\" -Name "SecureMFA" -ItemType "directory" -Force
    $SQLProcedureTemplate | Out-File C:\SecureMFA\sql_Create_WID_Database_SecureMfaOTP.sql -Force

    #write-host "Adding $adfs_service_account account into the Local Administrators group" -ForegroundColor Green
    #Add-LocalGroupMember -Group "Administrators" -Member $adfs_service_account -ErrorAction SilentlyContinue

    #Select CN from certificate to use with ADFS FederationServiceName
    $adfs_service_name  = Get-ChildItem -Path cert:\LocalMachine\My\$adfs_certificate_thumbprint | select subject | Select-String -Pattern 'CN\=([^},\r\n]+)' | %{$_.Matches.Groups[1].value}
    write-host "ADFS service name $adfs_service_name has been selected using CN value from $adfs_certificate_thumbprint certificate" -ForegroundColor Green

    #ADFS Install
    write-host "Installing ADFS service on a single server" -ForegroundColor Green
    Add-WindowsFeature ADFS-Federation -Includemanagementtools
    $install = Install-AdfsFarm -CertificateThumbprint $adfs_certificate_thumbprint -FederationServiceName $adfs_service_name -ServiceAccountCredential $fscredential
    sleep -Seconds 10
    write-host "Installation status:" $install.Status
    write-host $install.Message -ForegroundColor Yellow

    #Enable AUdit
    AUDITPOL /SET /SUBCATEGORY:"Application Generated" /FAILURE:ENABLE /SUCCESS:ENABLE 
    #Enable SAML 2.0 IdP-initiated sign-on
    set-adfsproperties -EnableIdpInitiatedSignon $True

    #Test ADFS Service installation
    Test-AdfsFarmInstallation -FederationServiceName $adfs_service_name -ServiceAccountCredential $fscredential

    write-host "Starting SecureMFA OTP Install" -ForegroundColor Green
    #SQL WID Configuration on localhost
    Invoke-Sqlcmd -ServerInstance "localhost\SQLEXPRESS" -InputFile "C:\SecureMFA\sql_Create_WID_Database_SecureMfaOTP.sql"
    
    #Deploy SecureMFA OTP Provider
    Install-SecureMfaOtpProvider

    # Verify your ADFS service by navigating into following URLs using your browser:
    write-host "Installation of ADFS service $adfs_service_name complete." -ForegroundColor Green
    write-host "ADFS service endpoints for FederationMetadata:" -ForegroundColor Green
    write-host "https://$adfs_service_name/FederationMetadata/2007-06/FederationMetadata.xml" -ForegroundColor Cyan
    write-host "https://$adfs_service_name/adfs/ls/IdpInitiatedSignon.aspx" -ForegroundColor Cyan

    #ADFS Application Group configuration
    New-AdfsApplicationGroup -Name "SecureMFA" -ApplicationGroupIdentifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -Description "SecureMFA OTP WebPortal"
    Add-AdfsNativeClientApplication -ApplicationGroupIdentifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -Name "SecureMFA - Native application" -Identifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -RedirectUri (("http://" + (([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname).tolower() + "/"),("http://" + ($env:computerName).tolower() + "/")) -Description "SecureMFA OTP WebPortal"
    Get-AdfsApplicationGroup -Name SecureMFa | Add-AdfsWebApiApplication -Name "SecureMFA - Web application" -Identifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -AccessControlPolicyName "Permit everyone and require MFA" -IssueOAuthRefreshTokensTo "AllDevices" -AllowedClientTypes "Public, Confidential"
    Grant-AdfsApplicationPermission -ClientRoleIdentifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -ServerRoleIdentifier "498a47d9-bbae-439e-9ffd-4f1afc609350" -ScopeNames "openid" -Description "SecureMFA WebPortal"

    #Deploy IIS Service with SecureMFA OTP WebPortal
    Install-WindowsFeature -Name Web-Server
    Remove-Item C:\inetpub\wwwroot\*.* -Force

    Expand-Archive -LiteralPath $zipfilepath -DestinationPath C:\inetpub\wwwroot -Force
    $ServiceHost = (([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname).tolower()   
    (Get-Content -path C:\inetpub\wwwroot\App\Scripts\app.js -Raw) -replace 'adfs.mydomain.com',$adfs_service_name| Set-Content -Path C:\inetpub\wwwroot\App\Scripts\app.js

    #####################
    write-host "Single Node DEMO ADFS Deployment With SecureMFA OTP Provider Is Complete." -ForegroundColor Green
        
    }
    catch
    {
        Write-Host "$($MyInvocation.InvocationName): $_" -ForegroundColor red
    }    


}