#Requires -Version 3 -RunAsAdministrator
#Requires -Modules ActiveDirectory, GroupPolicy


<#
.Synopsis
Automatic deploy of LAPS (Local Administrator Password Solution)
.DESCRIPTION
Automatic deployment of the MS LAPS application, including preparation of a shared folder for application installation on workstations. Creating a GPO object and linking it to Organization Units.
.EXAMPLE
Automated-LAPS-Install -ComputerOU "OU=Computers,OU=CONTOSO,DC=Contoso,DC=local"
.EXAMPLE
Automated-LAPS-Install -ComputerOU "OU=Computers,OU=CONTOSO,DC=Contoso,DC=local","OU=Laptops,OU=CONTOSO,DC=Contoso,DC=local"
.EXAMPLE
Automated-LAPS-Install -ComputerOU "OU=Computers,OU=CONTOSO,DC=Contoso,DC=local" -SecurityGroups "Workstation admins"
.INPUTS
Inputs to this cmdlet (if any)
.NOTES
#>


Param
(
    # ComputersOU - working dir for script and download assets
    [Parameter(Mandatory=$true,
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false,
        HelpMessage="Fill in DistinguishedName of OU, where computrers are located.",
        Position=0
    )]
    [ValidateNotNullOrEmpty()]
    [String[]]
    $ComputersOU,

    # SecurityGroup - working dir for script and download assets
    [Parameter(ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false,
        HelpMessage="Fill in DisplayName of security group, for view and reset computer's passwords.",
        Position=1
    )]
    [ValidateNotNullOrEmpty()]
    [String[]]
    $SecurityGroups = 'LAPS Admins',

    # GPOLink - Assign GPO policy to computeres OUs
    [Switch]
    $GPOLink = $true,
    
    # WorkFolderPath - working dir for script and download assets
    [String]
    $WorkFolderPath = (Join-Path -Path $env:homedrive -ChildPath 'Windows\Temp\LAPS'),

    # ShareFolder - folder from saving installation files for deployment on workstations
    [String]
    $ShareFolder,

    # GPOName - Name of GPO policy object in GPOMC
    [String]
    $GPOName = 'LAPS',

    # DownloadURLLAPS - URL for downloading MS LAPS software
    [String]
    $DownloadURLLAPS = 'https://download.microsoft.com/download/C/7/A/C7AAD914-A8A6-4904-88A1-29E657445D03/LAPS.x64.msi',

    # LAPSFileName - Name of MS LAPS instalator file
    [String]
    $LAPSFileName = 'LAPS.x64.msi',

    # DownloadURLMST - URL for downloading Transfrom file for MSI installation
    [String]
    $DownloadURLMST = 'https://gitlab.tslab.cz/howto/laps/-/raw/master/createCustomAdministrator.mst',

    # MSTFileName - Name of MST transform file
    [String]
    $MSTFileName = 'createCustomAdministrator.mst',

    # DownloadURLGPOBackup - URL for downloading GPO backup file
    [String]
    $DownloadURLGPOBackup = 'https://gitlab.tslab.cz/howto/laps/-/raw/master/GPO-LAPS.zip',

    # GPOBackupZipFileName - Name of GPO backup zip file
    [String]
    $GPOBackupZipFileName = 'GPO-LAPS.zip',

    # TranscriptFileName - File name of script log
    [String]
    $TranscriptFileName = 'Script.log',

    # $InstallLAPSLogFileName - File name of LAPS installation log
    [String]
    $InstallLAPSLogFileName = 'InstallLAPS.log',

    # GPOTemplatesCentralStore - Copy ADMX a ADML templates to Central Store
    [Switch]
    $GPOTemplatesCentralStore = $true

)

         
Begin
{
    $ErrorActionPreference = "Stop"

    #Start Transcript
    Start-Transcript -Path (Join-Path -Path $WorkFolderPath -ChildPath $TranscriptFileName)

    #Script start running time
    $StartScriptTime = Get-Date


    #### FUNCTIONS ###
    #Sending messages to console
    function Write-Message([string]$Message, [ValidateSet("Info","Warning”,"Error","Success")]$Severity="Info")
    {
        [string]$Time = (Get-Date -Format "HH:mm:ss").Trim()
        [string]$Count = ((Get-Date) - $StartScriptTime)

        switch($Severity)
        {
            "Info" {Write-Host $Time"|"$Count "-" $Message; Break}
            "Warning" {Write-Host $Time"|"$Count "-" $Message -ForegroundColor Yellow; Break}
            "Error" {Write-Host $Time"|"$Count "-" $Message -ForegroundColor Red; Break}
            "Success" {Write-Host $Time"|"$Count "-" $Message -ForegroundColor Green; Break}
        }
    }

#### FUNCTIONS END ###

    #Find FQDN and NetBIOS names
    Write-Message -Message "Finding FQDN and NetBIOS name"
    $FQDN = (Get-ADDomain).DNSRoot
    $NetBIOSName = (Get-ADDomain).NetBIOSName 
    Write-Message -Message ('FQDN is: {0} and NetBIOS name is: {1}' -f $FQDN, $NetBIOSName)

    #Validate computers OU
    Write-Message -Message "Validate existece of Computers OUs"
    #Get default computer OU distinghuishedName
    # Reference https://support.microsoft.com/en-us/kb/324949
	$OUQuery = [adsisearcher]'(&(objectclass=domain))'
	$OUQuery.SearchScope = 'base'
	$OUQuery.FindOne().properties.wellknownobjects | ForEach-Object {
		if ($_ -match '^B:32:AA312825768811D1ADED00C04FD8D5CD:(.*)$')
		{
			$DefaultComputerOU = $Matches[1]
		}
	}

    foreach ($OU in $ComputersOU)
    {
        If (Get-ADOrganizationalUnit -Filter "distinguishedName -eq '$OU'")
        {
            Write-Message -Message ("OU {0} VALIDATED" -f $OU) -Severity Success
        }elseif($OU -eq $DefaultComputerOU)
        {
            Write-Message -Message "Using defautl OU Computers are not allowed. Plese move your computers to another OU" -Severity Error
            Exit
        }else
        {
            Write-Message -Message ("OU {0} doesn't exist" -f $OU) -Severity Error
            Write-Message -Message $(Stop-Transcript)
            Exit

        }
    }
    
    #Validate Central Store folders
    If($GPOTemplatesCentralStore)
    {
        If(!(Test-Path -Path "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions"))
        {
            Write-Message -Message "Folder PolicyDefinitions doesn't exist. Creating new one" -Severity Warning
            New-Item -ItemType Directory -Path "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions" | Out-Null
        }

        If(!(Test-Path -Path "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions\en-US"))
        {
            Write-Message -Message "Folder PolicyDefinitions\en-US doesn't exist. Creating new one" -Severity Warning
            New-Item -ItemType Directory -Path "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions\en-US" | Out-Null
        }
    }


    #Verify installation folder for LAPS
    Write-Message -Message "Verifying access to share folder."
    If(!($ShareFolder))
    {
        $ShareFolder = "\\$FQDN\NETLOGON\LAPS"
        Write-Message -Message ("Setting up installation folder {0} for LAPS" -f $ShareFolder)
        New-Item -ItemType Directory -Path $ShareFolder -Force
    }

    if(!(Test-Path -Path $ShareFolder))
    {
        Write-Message -Message ("Access denied to folder {0}" -f $ShareFolder) -Severity Error
        Exit
    }
    Write-Message -Message ("Access to folder {0} was VALIDATED" -f $ShareFolder) -Severity Success

    #Test existing path to working folder, if doesn't exist, create it
    Write-Message -Message ("Testing existing working folder in path {0}" -f $WorkFolderPath)
    If (!(Test-Path -Path $WorkFolderPath))
    {
	    Write-Message -Message ("Working folder doesn't exist. Creating folder in path: {0}" -f $WorkFolderPath) -Severity Warning
        New-Item -Path $WorkFolderPath -ItemType Directory -Force | Out-Null
    }

    #Find MS LAPS software
    Write-Message -Message "Finding MS LAPS intalator file ($LAPSFileName)"
    If (!(Test-Path -Path (Join-Path -Path $ShareFolder -ChildPath $LAPSFileName)))
    {
        Write-Message -Message ("{0} not found. Starting downloading ..." -f $LAPSFileName)
        #Download MS LAPS software
        Write-Message -Message "Downloading software MS LAPS"
        $DownloadStartTime = Get-Date
        Invoke-WebRequest -Uri $DownloadURLLAPS -OutFile (Join-Path -Path $ShareFolder -ChildPath $LAPSFileName)
        Write-Message -Message ('Downloaded in: {0} second(s)' -f ((Get-Date) - $DownloadStartTime)) -Severity Success
    
    }else{
        Write-Message -Message "$LAPSFileName found in shared folder" -Severity Success
    }

    #Find MST Transform file
    Write-Message -Message ("Finding MST Transform file ({0})" -f $MSTFileName)
    If (!(Test-Path -Path (Join-Path -Path $ShareFolder -ChildPath $MSTFileName)))
    {
        Write-Message -Message ("{0} not found. Starting downloading ..." -f $MSTFileName)
        #Download MST Transform file
        Write-Message -Message "Downloading MST Transform file"
        $DownloadStartTime = Get-Date
        #Certificate work around
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Invoke-WebRequest -Uri $DownloadURLMST -OutFile (Join-Path -Path $ShareFolder -ChildPath $MSTFileName)
        Write-Message -Message ('Downloaded in: {0} second(s)' -f ((Get-Date) - $DownloadStartTime)) -Severity Success
    
    }else{
        Write-Message -Message ("{0} found in shared folder" -f $MSTFileName) -Severity Success
    }

    #Find GPO backup file zip
    Write-Message -Message ("Finding GPO backup zip file ({0})" -f $GPOBackupZipFileName)
    If (!(Test-Path -Path (Join-Path -Path $WorkFolderPath -ChildPath $GPOBackupZipFileName)))
    {
        Write-Message -Message ("{0} not found. Starting downloading ..." -f $GPOBackupZipFileName)
        #Download MST Transform file
        Write-Message -Message "Downloading GPO backup file"
        $DownloadStartTime = Get-Date
        #Certificate work around
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Invoke-WebRequest -Uri $DownloadURLGPOBackup -OutFile (Join-Path -Path $WorkFolderPath -ChildPath $GPOBackupZipFileName)
        Write-Message -Message ('Downloaded in: {0} second(s)' -f ((Get-Date) - $DownloadStartTime)) -Severity Success
    
    }else{
        Write-Message -Message ("{0} found in shared folder" -f $GPOBackupZipFileName) -Severity Success
    }

    #Unpack GPO backupfile
    Write-Message -Message ("Unpacking GPO backup file {0}" -f $GPOBackupZipFileName)
    Expand-Archive -LiteralPath (Join-Path -Path $WorkFolderPath -ChildPath $GPOBackupZipFileName) -DestinationPath $WorkFolderPath -Force

    #Add current user as a member Schema Admins group
    Write-Message -Message ("Adding current user {0} as a member Schema Admins group" -f $env:UserName)
    Add-ADGroupMember -Identity "Schema Admins" -Members  $env:UserName| Out-Null
    
    Write-Message -Message "Preparing for installation is complete." -Severity Success
}
Process
{
    #Installation MS LAPS Software
    Write-Message -Message "Starting installation MS LAPS Software"
    Start-Process -FilePath 'msiexec' -ArgumentList ('/i {0} /passive /l*v "{1}" ADDLOCAL=Management,Management.UI,Management.PS,Management.ADMX' -f (Join-Path -Path $ShareFolder -ChildPath $LAPSFileName), (Join-Path -Path $WorkFolderPath -ChildPath $InstallLAPSLogFileName)) -Wait

    #Import LAPS powershell module
    Write-Message -Message "Importing LAPS powershell module"
    Import-Module -Name AdmPwd.PS | Out-Null

    #Update AD schme, add new fields to store password data
    Update-AdmPwdADSchema

    #Find security groups
    foreach($SecurityGroup in $SecurityGroups)
    {
        Write-Message -Message ("Searching security group {0}" -f $SecurityGroup)
        If (!($Group = Get-ADGroup -Filter {Name -eq $SecurityGroup}))
        {
            Write-Message -Message ("Security Group {0} doesn't exist. Creating new Security Group {0}" -f $SecurityGroup) -Severity Warning
            New-ADGroup -Name $SecurityGroup -GroupCategory Security -GroupScope Global
            
            Write-Message -Message ("Add current user {0} as a member security group {1}" -f $env:UserName, $SecurityGroup)
            Add-ADGroupMember -Identity $SecurityGroup -Members $env:UserName
        }else
        {
            Write-Message -Message ("Security group {0} was found" -f $SecurityGroup) -Severity Success
        }
    }

    #Configure self permission to add passwords
    foreach($OU in $ComputersOU)
    {
        Write-Message -Message ("Configure self permission for OU {0}" -f $OU)
        Set-AdmPwdComputerSelfPermission -Identity $OU
    }

    # Configure who can read and reset the password. By default only domain/enterprise admins can.
    foreach($OU in $ComputersOU)
    {
        foreach($SecurityGroup in $SecurityGroups)
        {
            Write-Message -Message ("Configure read and reset password permissions for {0} in OU {1}" -f $SecurityGroup, $OU)
            Set-AdmPwdReadPasswordPermission -Identity $OU -AllowedPrincipals $SecurityGroup
            Set-AdmPwdResetPasswordPermission -Identity $OU -AllowedPrincipals $SecurityGroup  
        } 
    }

    #Copy ADML and ADMX files to central store
    If($GPOTemplatesCentralStore)
    {
        Write-Message -Message "Copying ADMX and ADML templates to GPO Central Store"
        Copy-Item -Path (Join-Path -Path $env:windir -ChildPath "PolicyDefinitions\AdmPwd.admx") -Destination "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions" -Force
        Copy-Item -Path (Join-Path -Path $env:windir -ChildPath "PolicyDefinitions\en-US\AdmPwd.adml") -Destination "\\$FQDN\SYSVOL\$FQDN\Policies\PolicyDefinitions\en-US\" -Force
        
    }else
    {
        Write-Message -Message "Skipping copy of ADMX and ADML templates files"
    }


    #Prepare GPO
    #Reference https://gallery.technet.microsoft.com/Migrate-Group-Policy-2b5067d8#content

    #Change variables in the GPO migration table to suit environment by recursing through the migration table and then changing the values to suit the current environment.
    Write-Message -Message "Modifying GPO migration table"
    $MigrationTable =  "$WorkFolderPath\Migration.migtable"
    (Get-Content $MigrationTable).replace("\\SHAREFOLDER", "$ShareFolder") | Set-Content $MigrationTable

    #Import GPO
    #Import the actual GPO
    Write-Message -Message "Importing GPO policy"            
    Import-GPO -CreateIfNeeded -path "$WorkFolderPath" -BackupId '{02EC9DDA-3D5F-44FD-BEC2-7D7BA2A64DFF}' -TargetName $GPOName -MigrationTable "$WorkFolderPath\Migration.migtable"


    #Link GPO na OU computers
    If($GPOLink)
    {
        foreach($OU in $ComputersOU)
        {
            If(!(Get-GPInheritance -Target $OU | select -ExpandProperty GpoLinks | ?{$_.DisplayName -eq $GPOName}))
            {
                Write-Message -Message ("Creating GPO link to OU {0}" -f $OU)
                New-GPLink -Name $GPOName -Target $OU
            }else{
                Write-Message -Message ("GPO {0} is already linked in OU {1}. Skipping" -f $GPOName, $OU) -Severity Warning
            }
            
        }
    }else
    {
        Write-Message -Message "Skipping GPO link to Computers OU"
    }
    
}
End
{
    Write-Message -Message "Deplyment is done, starting cleanning."

    #Remove current user as a member Schema Admins group
    Write-Message -Message "Removing current user $env:UserName from Schema Admins group"
    Remove-ADGroupMember -Identity "Schema Admins" -Members  $env:UserName -Confirm:$false | Out-Null

    #Stop Transcript
    Write-Message -Message $(Stop-Transcript)
    
    #Delete working folder
    Write-Message -Message ("Deleting temporary files in {0}" -f $WorkFolderPath)
    Remove-item -Path $WorkFolderPath -Confirm:$false -Recurse -Force

    Write-Message -Message "Deployment is done!" -Severity Success
}