LAPS.Nano.DSC.psm1

enum Ensure
{
    Absent
    Present
}

[DscResource()]
class cLapsNanoInstall
{
    <#
    This resource ensures installation/uninstallation of LAPS.Nano client.
    All necessary files and data for LAPS.Nano client are delivered as a part of package downloaded from PS Gallery,
    so resource just takes files from its own package and uses them for installation
 
    Creation of DSC Configuration for this resource is pretty simple and looks like below - see Config/LAPS.Nano.DSC.Install.ps1
    Ensure = Present means that resource ensures that LAPS client is installed
    Ensure = Absent means that resource ensures that LAPS client is uninstalled if it finds it's installed
 
    Installation status is detected as follows:
    - Service LAPS.Nano exists
    - Service executable has proper version
    - Service event manifest is registered
 
    For detailed instructions, see https://blogs.msdn.microsoft.com/laps/2016/05/10/laps-and-nano-server/
 
    Sample configuration:
    ---
    $ConfigData = @{
       AllNodes = @(
          @{ NodeName = "*" }
            )
    }
 
    Configuration LAPS_Nano_Install
    {
       Param (
  
          [Parameter()]
          [ValidateSet("Present","Absent")]
          [String]$Ensure = "Present"
       )
    
       Import-DscResource -ModuleName LAPS.Nano.DSC -ModuleVersion '#version'
 
       cLapsNanoInstall Install
       {
        ID="LAPS.Nano"
        Ensure = $Ensure
       }
     
    }
 
    LAPS_Nano_Install -ConfigurationData $ConfigData -Ensure Present
    ---
 
    #>



    #static identifier
    [DscProperty(Key)]
    [String]$ID
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure    

    #keep file location aligned with LAPS
    [DscProperty(NotConfigurable)]
    [String]$TargetFolder="$env:ProgramFiles\AdmPwd\CSE"

    [void]Set()
    {
        Write-verbose '[Set]Entering'
        
        Write-verbose '[Set] Detecting service state and stopping, if necessary'
        $svc=Get-Service LAPS.Nano -ErrorAction:SilentlyContinue
        if(($null -ne $svc) -and ($svc.Status -eq 'Running'))
        {
            Stop-Service LAPS.Nano -Force
            # just sleep for a while to be sure that service stops and executable is free for replacement/removal
            Start-Sleep -Seconds 2
        }
        if($this.Ensure -eq [Ensure]::Present)
        {
            [String]$SourcePath = Split-Path $script:MyInvocation.MyCommand.Path
            if(-not [System.IO.Directory]::Exists($this.TargetFolder))
            {
                New-Item "$($this.TargetFolder)" -ErrorAction SilentlyContinue
            }
            #C++ runtime
            Write-verbose '[Set] Copying C++ runtime'
            if(-not (Test-Path "$env:SystemRoot\System32\vcruntime140.dll"))
            {
                Copy-Item -Path "$SourcePath\Redist\vcruntime140.dll" -Destination "$env:SystemRoot\System32" -Force | Out-Null
            }
            if(-not (Test-Path "$env:SystemRoot\System32\msvcp140.dll"))
            {
                Copy-Item -Path "$SourcePath\Redist\msvcp140.dll" -Destination "$env:SystemRoot\System32" -Force | Out-Null
            }
            
            #LAPS.Nano executable
            Write-verbose '[Set] Copying LAPS.Nano client executable'
            Copy-Item -Path "$SourcePath\Runtime\LAPS.Nano.Service.exe" -Destination "$($this.TargetFolder)" -Force | Out-Null

            #ETW manifest
            Write-verbose '[Set] Copying LAPS.Nano ETW manifest'
            Copy-Item -Path "$SourcePath\Runtime\Messages.man" -Destination "$($this.TargetFolder)" -Force | Out-Null

            #LAPS event manifest registration
            Write-verbose '[Set] Registering LAPS.Nano ETW manifest'
            wevtutil um "$($this.TargetFolder)\Messages.man"
            wevtutil im "$($this.TargetFolder)\Messages.man" /rf:"$($this.TargetFolder)\Laps.Nano.Service.exe" /mf:"$($this.TargetFolder)\Laps.Nano.Service.exe"

            #Create LAPS client service - only if it does not exist
            if($null -eq $svc)
            {
                Write-verbose '[Set] Creating service'
                New-Service -Name LAPS.Nano -BinaryPathName "$($this.TargetFolder)\LAPS.Nano.Service.exe" -DisplayName LAPS.Nano -Description "LAPS Client for Nano Server" -StartupType  Automatic | Out-Null
            }
            Write-verbose '[Set] Starting service'
            Start-Service LAPS.Nano | Out-Null  
        }
        else 
        {
            #remove service registration
            if($null -ne $svc)
            {
                Write-verbose '[Set] Removing service'
                sc.exe delete LAPS.Nano
            }
            
            #unregister ETW manifest
            Write-verbose '[Set] Unregistering ETW manifest'
            if(Test-Path "$($this.TargetFolder)\Messages.man")
            {
                wevtutil um "$($this.TargetFolder)\Messages.man"
            }
            
            #remove service files
            Write-verbose '[Set] Removing LAPS binaries'
            Remove-Item "$($this.TargetFolder)\Messages.man" -ErrorAction SilentlyContinue
            Remove-Item "$($this.TargetFolder)\LAPS.Nano.Service.exe" -ErrorAction SilentlyContinue
            Remove-Item "$($this.TargetFolder)" -ErrorAction SilentlyContinue
            
            #we're keeping C++ runtime in place - may be used by other apps - so no removal of its binaries

        }
        Write-verbose '[Set]Exiting'
    }
    
    [cLapsNanoInstall]Get()
    {
        Write-verbose '[Get]Entering'

        Write-verbose '[Get]Exiting'
        return $this
        
    }
    
    [bool]Test()
    {
        Write-verbose '[Test]Entering'
        
        [bool]$retVal = $this.GetConfigStatus()

        Write-verbose "[Test]Returning $retVal"
        return $retVal
    }
    
    [bool]GetConfigStatus()
    {
        #return true is LAPS.Nano client side is properly installed
        Write-verbose "[GetConfigStatus]Entering"
        [bool]$serviceInstalled=$false
        [bool]$versionUpToDate=$false
        [bool]$manifestRegistered=$false

        
        #check service installation status
        $serviceInstalled = ($null -ne (Get-Service -Name LAPS.Nano -ErrorAction SilentlyContinue))
        Write-verbose "[GetConfigStatus] ServiceInstallStatus: $serviceInstalled"
        
        #check solution version
        #only if solution is present
        if($this.Ensure -eq 'Present' -and [System.IO.File]::Exists("$($this.TargetFolder)\LAPS.Nano.Service.exe"))
        {
               [String]$SourcePath = Split-Path $script:MyInvocation.MyCommand.Path

            $versionUpToDate = (([System.Diagnostics.FileVersionInfo]::GetVersionInfo("$($this.TargetFolder)\LAPS.Nano.Service.exe")).ProductVersion -ge ([System.Diagnostics.FileVersionInfo]::GetVersionInfo("$SourcePath\Runtime\LAPS.Nano.Service.exe")).ProductVersion)
            Write-verbose "[GetConfigStatus] SolutionVersionCheckStatus: $versionUpToDate"
        }

        #check event manifest registration
        $key=[microsoft.win32.registry]::LocalMachine.OpenSubKey('System\CurrentControlSet\Services\EventLog\Application\AdmPwd')
        $manifestRegistered = ($null -ne $key)
        Write-verbose "[GetConfigStatus] ManifestRegistrationStatus: $manifestRegistered"
        
        if($this.Ensure -eq "Present")
        {
            return $serviceInstalled -and $versionUpToDate -and $manifestRegistered
        }
        else
        {
            return -not ($serviceInstalled -or $manifestRegistered)
        }
        Write-verbose "[GetConfigStatus]Exiting"
    }
}