DSCResources/ArcGIS_Service_Account/ArcGIS_Service_Account.psm1

<#
    .SYNOPSIS
        Resource to make the Data Directories accesssible to given Run as Account for a given ArcGIS Component.
    .PARAMETER Ensure
        Take the values Present or Absent.
        - "Present" ensures the Data Directories are accesssible to given Run as Account for a given ArcGIS Component.
        - "Absent" ensures the Data Directories are made in accesssible to given Run as Account for a given ArcGIS Component(Not Implemented).
    .PARAMETER Name
        Name of the ArcGIS Component that is being configured.
    .PARAMETER RunAsAccount
        A MSFT_Credential Object - Run as Account, Account to which the DataDir Folders be made accessible
    .PARAMETER DataDir
        Data Directory paths to which the necessary permissions need to be given.
#>


function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name
    )

    Import-Module $PSScriptRoot\..\..\ArcGISUtility.psm1 -Verbose:$false

    $null
}


function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $RunAsAccount,

        [System.String[]]
        $DataDir,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    Import-Module $PSScriptRoot\..\..\ArcGISUtility.psm1 -Verbose:$false

    if($Ensure -ieq 'Present') {
        $RunAsUserName = $RunAsAccount.UserName
        $RunAsPassword = $RunAsAccount.GetNetworkCredential().Password
   
        Write-Verbose "RunAsAccount Username:- $RunAsUserName"
        if($RunAsUserName -and $RunAsUserName.StartsWith('.\')){            
            $RunAsUserName = $RunAsUserName.Substring(2) # Remove the current machine prefix
            Write-Verbose "Removing the machine prefix for the RunAsAccount to $RunAsUserName"
        }

        $RegKey = Get-EsriRegistryKeyForService -ServiceName $Name
        $InstallDir = (Get-ItemProperty -Path $RegKey -ErrorAction Ignore).InstallDir
        Write-Verbose "Install Dir for $Name is $InstallDir"
        if($InstallDir) 
        { 
           $InstallDir = $InstallDir.TrimEnd('\')
           if(Test-Path $InstallDir) 
           {
                if(<#$Name -ieq 'ArcGIS Server' -OR $Name -ieq 'Portal for ArcGIS' -or#> $Name -ieq 'ArcGIS Data Store'){
                    $ExecPath = $InstallDir
                    $Arguments = "/username $($RunAsUserName) /password $($RunAsPassword)"
                    Write-Verbose "Just Checking : - $Arguments"
                    if($Name -ieq 'ArcGIS Server'){
                        $ExecPath = Join-Path $ExecPath '\\bin\\ServerConfigurationUtility.exe'
                    }elseif($Name -ieq 'Portal for ArcGIS'){
                        $ExecPath = Join-Path $ExecPath '\\tools\\ConfigUtility\\configureserviceaccount.bat'
                    }
                    elseif($Name -ieq 'ArcGIS Data Store'){
                        $ExecPath = Join-Path $ExecPath '\\tools\\configureserviceaccount.bat'
                    }
                    Write-Verbose "Providing RunAs Account '$RunAsUserName' has the required permissions to $InstallDir"
                    Write-Verbose "configureserviceaccount $InstallDir /grant $($RunAsUserName):(OI)(CI)F"
                    
                    $psi = New-Object System.Diagnostics.ProcessStartInfo
                    $psi.FileName = $ExecPath
                    $psi.Arguments = $Arguments
                    $psi.UseShellExecute = $false #start the process from it's own executable file
                    $psi.RedirectStandardOutput = $true #enable the process to read from standard output
                    $psi.RedirectStandardError = $true #enable the process to read from standard error
                    $psi.EnvironmentVariables["AGSPORTAL"] = [environment]::GetEnvironmentVariable("AGSPortal","Machine")
                    $psi.EnvironmentVariables["AGSDATASTORE"] = [environment]::GetEnvironmentVariable("AGSDATASTORE","Machine")
                    $psi.EnvironmentVariables["AGSSERVER"] = [environment]::GetEnvironmentVariable("AGSSERVER","Machine")
                    

                    $p = [System.Diagnostics.Process]::Start($psi)
                    $p.WaitForExit()
                    $op = $p.StandardOutput.ReadToEnd()
                    if($op -and $op.Length -gt 0) {
                        Write-Verbose "Output of execution:- $op"
                    }
                    $err = $p.StandardError.ReadToEnd()
                    
                    if($p.ExitCode -eq 0) {                    
                        Write-Verbose "Initialized correctly indicating successful desktop initialization"
                        $result = $true
                    }else {
                        Write-Verbose "Initialization did not succeed. Process exit code:- $($p.ExitCode) $p"
                    }

                }else{
                    $acl = Get-Acl $InstallDir | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"} | Where {$_.FileSystemRights -ieq "FullControl"}
                    if((-not($acl)) -or ($acl.AccessControlType -ine 'Allow')) {
                        Write-Verbose "Providing RunAs Account '$RunAsUserName' has the required permissions to $InstallDir"
                        Write-Verbose "icacls.exe $InstallDir /grant $($RunAsUserName):(OI)(CI)F"
                        icacls.exe $InstallDir /grant "$($RunAsUserName):(OI)(CI)F"
                    }else {
                        Write-Verbose "RunAs Account '$RunAsUserName' has the required permissions to $InstallDir"
                    }    
                }       
           }
        }

        if($DataDir) 
        {
            foreach($DataDirectory in $DataDir) 
            {
                $LocalPath = $DataDirectory
                if($LocalPath.StartsWith('HKLM:\')) {
                    $LocalPath = (Get-Item ((Get-ItemProperty ($LocalPath) -ErrorAction Ignore).ContentDir))                        
                }elseif($LocalPath.StartsWith('$env:')){
                    $LocalPath = $ExecutionContext.InvokeCommand.ExpandString("$DataDirectory")
                }
                if($LocalPath -and (Test-Path $LocalPath)) 
                {
                    Write-Verbose "Checking Permissions on $LocalPath"
                    $acl = Get-Acl $LocalPath | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"} | Where {$_.FileSystemRights -ieq "FullControl"}
                    if((-not($acl)) -or ($acl.AccessControlType -ine 'Allow')) {
                        Write-Verbose "Permissions are not set for $LocalPath"
                        Write-Verbose "Providing RunAs Account '$RunAsUserName' the required permissions to $LocalPath"
                        Write-Verbose "icacls.exe $LocalPath /grant $($RunAsUserName):(OI)(CI)F"
                        icacls.exe $LocalPath /grant "$($RunAsUserName):(OI)(CI)F"
                    }  else {
                        Write-Verbose "RunAs Account '$RunAsUserName' has the required permissions to $LocalPath"
                    }             
                }                
            }
        }

        if($Name -ieq 'ArcGISGeoEvent') {
            ###
            ### GeoEvent needs additional permissions set and delete zookeeper folder
            ###
            $GeoEventProgramData = Join-Path $env:ProgramData 'Esri\GeoEvent'            
            if(Test-Path $GeoEventProgramData) {
                $ZooKeeperFolder = Join-Path $GeoEventProgramData 'zookeeper'
                if(Test-Path $ZooKeeperFolder) {
                    Write-Verbose "Deleting ZooKeeper folder $ZooKeeperFolder"
                    Remove-Item -Path $ZooKeeperFolder -Recurse -Force 
                }
                $ZooKeeperFile = Join-Path $GeoEventProgramData 'zookeeper.properties'
                if(Test-Path $ZooKeeperFile) {
                    Write-Verbose "Deleting ZooKeeper file $ZooKeeperFile"
                    Remove-Item -Path $ZooKeeperFile -Recurse -Force -ErrorAction Ignore
                }
                $ZooKeeperFile = Join-Path $GeoEventProgramData 'zookeeper-dynamic.properties'
                if(Test-Path $ZooKeeperFile) {
                    Write-Verbose "Deleting ZooKeeper file $ZooKeeperFile"
                    Remove-Item -Path $ZooKeeperFile -Recurse -Force -ErrorAction Ignore
                }
            }

            #Add a check for 10.6

            ###
            ### GeoEvent Clean up - the karaf data folder and ProgramData (after stopping the service)
            ### GeoEventGateway Clean up - the Gatewaylog and ProgramData (after stopping the service)
            ###
            @('ArcGISGeoEvent', 'ArcGISGeoEventGateway') | %{
                try {                
                    $ServiceName = $_
                    Write-Verbose "Restarting Service $ServiceName"
                    Stop-Service -Name $ServiceName -Force -ErrorAction Ignore
                    Write-Verbose 'Stopping the service' 
                    Wait-ForServiceToReachDesiredState -ServiceName $ServiceName -DesiredState 'Stopped'    
                    Write-Verbose 'Stopped the service'            
                }catch {
                    Write-Verbose "[WARNING] Stopping Service $_"
                }
            }
            
            if(-not($InstallDir)) {
                # GeoEvent is always installed in Server's install directory
                $RegKey = Get-EsriRegistryKeyForService -ServiceName 'ArcGIS Server'
                $InstallDir = (Get-ItemProperty -Path $RegKey -ErrorAction Ignore).InstallDir
            }
            
            if($InstallDir) {
                $InstallDir = Join-Path $InstallDir 'GeoEvent'
                $GeoEventKarafDataFolder = Join-Path $InstallDir 'data'        
                
                $GeoEventGatewayLogFolder = Join-Path $InstallDir 'gateway\log'    

                $GeoEventProgramData = Join-Path $env:ProgramData 'Esri\GeoEvent'        
                $GeoEventGatewayProgramData = Join-Path $env:ProgramData 'Esri\GeoEvent-Gateway'

                @($GeoEventKarafDataFolder, $GeoEventGatewayLogFolder, $GeoEventProgramData, $GeoEventGatewayProgramData) | %{ 
                    $FolderToDelete = $_
                    Write-Verbose "Clean up Folder:- $FolderToDelete"
                    if(Test-Path $FolderToDelete) {
                        Write-Verbose "Recursively delete Folder:- $FolderToDelete"
                        Get-ChildItem -Path $FolderToDelete | %{ Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Ignore }
                    }
                }
            }
            
            ###
            ### At 10.5 and beyond GE uses the Zookeeper component of ArcGIS Server which stores in local
            ###
            $ZooKeeperFolder = Join-Path $env:SystemDrive 'arcgisserver\local\zookeeper'
            if(Test-Path $ZooKeeperFolder) {
                Write-Verbose "Deleting ZooKeeper folder $ZooKeeperFolder in local path"
                Remove-Item -Path $ZooKeeperFolder -Recurse -Force 
            }
        }


        <#
        if(-not($DataDir) -and ($Name -ieq 'Portal for ArcGIS')) {
            $DataDir = (Get-Item ((Get-ItemProperty ("HKLM:\SOFTWARE\ESRI\$Name")).ContentDir))
        }
        if($DataDir)
        {
            $DataDir = $DataDir.TrimEnd('\')
            if(Test-Path $DataDir)
            {
                $acl = Get-Acl $DataDir | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"}
                if((-not($acl)) -or ($acl.FileSystemRights -ine 'FullControl') -or ($acl.AccessControlType -ine 'Allow')) {
                    Write-Verbose "Providing RunAs Account '$RunAsUserName' has the required permissiones to $DataDir"
                    Write-Verbose "icacls.exe $DataDir /grant $($RunAsUserName):(OI)(CI)F"
                    icacls.exe $DataDir /grant "$($RunAsUserName):(OI)(CI)F"
 
                    ## TEMPORARY
                    if($Name -ieq 'Portal for ArcGIS') {
                        $DataDirParent = Split-Path -Path $DataDir -Parent
                        Write-Verbose "Providing RunAs Account '$RunAsUserName' has the required permissiones to $DataDirParent"
                        Write-Verbose "icacls.exe $DataDirParent /grant $($RunAsUserName):(OI)(CI)F"
                        icacls.exe $DataDirParent /grant "$($RunAsUserName):(OI)(CI)F"
                    }
                }else {
                    Write-Verbose "RunAs Account '$RunAsUserName' has the required permissiones to $DataDir"
                }
            }
        }
 
        if($Name -ieq 'ArcGISGeoEvent') {
            ###
            ### GeoEvent needs additional permissions set and delete zookeeper folder
            ###
            $GeoEventProgramData = Join-Path $env:ProgramData 'Esri\GeoEvent'
            if(Test-Path $GeoEventProgramData) {
                Write-Verbose "Program Data Dir for $Name is $GeoEventProgramData"
                $acl = Get-Acl $GeoEventProgramData | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"}
                if((-not($acl)) -or ($acl.FileSystemRights -ine 'FullControl') -or ($acl.AccessControlType -ine 'Allow')) {
                    Write-Verbose "Providing Required Permissions to Program Data Folder for RunAs Account"
                    Write-Verbose "icacls.exe $GeoEventProgramData /grant $($RunAsUserName):(OI)(CI)F"
                    icacls.exe $GeoEventProgramData /grant "$($RunAsUserName):(OI)(CI)F"
 
                    $ZooKeeperFolder = Join-Path $GeoEventProgramData 'zookeeper'
                    if(Test-Path $ZooKeeperFolder) {
                        Write-Verbose "Deleting ZooKeeper folder $ZooKeeperFolder"
                        Remove-Item -Path $ZooKeeperFolder -Recurse -Force
                    }
                } else {
                    Write-Verbose 'Permissions to Program Data Folder for RunAs Account are already correct'
                }
            }
        }
        #>


        if($Name -ine 'ArcGIS Data Store')  # No need to restart DataStore - the mandatory property change for 'failover_on_primary_stop' will take care of it
        {
            ###
            ### If the Service Credentials are changed. Restart the Service (just in case) TODO:- Revisit if this is needed
            ###
            try {                
                Write-Verbose "Restarting Service $Name"
                Stop-Service -Name $Name -Force -ErrorAction Ignore
                Write-Verbose 'Stopping the service' 
                Wait-ForServiceToReachDesiredState -ServiceName $Name -DesiredState 'Stopped'    
                Write-Verbose 'Stopped the service'            
            }catch {
                Write-Verbose "[WARNING] Stopping Service $_"
            }
            try {                
                Write-Verbose 'Starting the service'
                Start-Service -Name $Name -ErrorAction Ignore       
                Wait-ForServiceToReachDesiredState -ServiceName $Name -DesiredState 'Running'
                Write-Verbose "Restarted Service $Name"
            }catch {
                Write-Verbose "[WARNING] Starting Service $_"
            }
        }

    }else{
        Write-Warning 'Absent not implemented'
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $RunAsAccount,

        [System.String[]]
        $DataDir,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    Import-Module $PSScriptRoot\..\..\ArcGISUtility.psm1 -Verbose:$false

    $result = $true
    $RunAsUserName = $RunAsAccount.UserName
    $RegKey = Get-EsriRegistryKeyForService -ServiceName $Name
    $InstallDir =(Get-ItemProperty -Path $RegKey -ErrorAction Ignore).InstallDir    

    Write-Verbose "RunAsAccount Username:- $RunAsUserName"
    if($RunAsUserName -and $RunAsUserName.StartsWith('.\')){            
        $RunAsUserName = $RunAsUserName.Substring(2) # Remove the current machine prefix
        Write-Verbose "Removing the machine prefix for the RunAsAccount to $RunAsUserName"
    }

    if($InstallDir -and (Test-Path $InstallDir)) {
        Write-Verbose "Install Dir for $Name is $InstallDir"
        $acl = Get-Acl $InstallDir | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"} | Where {$_.FileSystemRights -ieq "FullControl"}
        if((-not($acl)) -or ($acl.AccessControlType -ine 'Allow')) {
            Write-Verbose "Permissions are not set for $InstallDir"
            $result = $false
        }
    }

    if($result) {
        if($DataDir) {
            foreach($DataDirectory in $DataDir) {
                if($result) {
                    $LocalPath = $DataDirectory
                    if($LocalPath.StartsWith('HKLM:\')) {
                        $LocalPath = (Get-Item ((Get-ItemProperty ($LocalPath) -ErrorAction Ignore).ContentDir))                        
                    }elseif($LocalPath.StartsWith('$env:')){
                        $LocalPath = $ExecutionContext.InvokeCommand.ExpandString("$DataDirectory")
                    }
                    if($LocalPath -and (Test-Path $LocalPath)) 
                    {
                        Write-Verbose "Checking Permissions on $LocalPath"
                        $acl = Get-Acl $LocalPath | Select-Object -ExpandProperty Access | Where {$_.IdentityReference -ieq "$env:ComputerName\$RunAsUserName"} | Where {$_.FileSystemRights -ieq "FullControl"}
                        if((-not($acl)) -or ($acl.AccessControlType -ine 'Allow')) {
                            Write-Verbose "Permissions are not set for $LocalPath"
                            $result = $false
                        }
                    }
                }
            }
        }
    }
    
    if($Ensure -ieq 'Present') {
           $result   
    }
    elseif($Ensure -ieq 'Absent') {        
        (-not($result))
    }
}


Export-ModuleMember -Function *-TargetResource