# createTaskScheduler.ps1
# What this script does:
# 1. Ask user for valid Domain Admin credentials
# 2. Connect to each servers on a list
# 3. Copy Script file from a UNC path onto local C:\Scripts directory(ies)
# 4. Add a scheduled task on each machine using the Domain Administrator credential
# Requires: Windows 2012 or higher

# Scheduled task variables
$computers='SERVER01','SERVER02','SERVER03'
$scriptFile='\\JUMPBOX\C$\Scripts\asyncMaintenance.ps1'
$description="Put Some Descriptive Words Here"
$taskName="Task by $env:username"
$repeatIntervalMinutes=60

# Obtain Domain Admin Credentials
function obtainDomainAdminCredentials{
    # Legacy domain binding function
    function isValidCred($u,$p){
        # Get current domain using logged-on user's credentials
        $domain = "LDAP://" + ([ADSI]"").distinguishedName
        $domainCred = New-Object System.DirectoryServices.DirectoryEntry($domain,$u,$p)
        if ($domainCred){return $True}else{return $False}
    }
    function isDomainAdmin($username){
        if (!(get-module activedirectory)){Install-WindowsFeature RSAT-AD-PowerShell -Confirm:$false}
        if((Get-ADUser $username -Properties MemberOf).MemberOf -match 'Domain Admins'){return $True;}else{return $false;}
    }

    # Create a domain context
    function dotNetDomainBind{
        Add-Type -AssemblyName System.DirectoryServices.AccountManagement;  
        Try {
            $type = [System.DirectoryServices.AccountManagement.ContextType]::Domain;
            $context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $type,$env:USERDOMAIN;
            return $context;
        } Catch {
            If ($_.Exception.InnerException -like "*The server could not be contacted*") {
                write-host "Could not contact a server for the specified domain $env:USERDOMAIN via DotNet Method.";
            } Else {
                write-host "Unknown errors occured while attempting to contact $env:USERDOMAIN via DotNet Method.";
            }
            return $false;
        }
    }

    $attempt=0;
    $maxAttempts=3;
    $plainTextPassword='';   
    Do{
        $attempt++;
        $failureMessage = $null;

        [string][ValidateNotNullOrEmpty()]$userName=Read-Host -Prompt "Please input a User ID";
        if($userName -match '\\'){$username=$env:USERDOMAIN+'\'+$userName}
        $password = Read-Host -Prompt "Please type in the password for user $userName" -AsSecureString;
        $plainTextPassword=[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
    
        # Test bind to this credential
        try {
            # Test bind
            $context=dotNetDomainBind
            if($context){
                $validatedAccount = $context.ValidateCredentials($userName,$plainTextPassword)
                }else{
                    $validatedAccount=isValidCred -u $userName -p $plainTextPassword
                    }
            
            If ($validatedAccount) {
                $isDomainAdmin=isDomainAdmin -username $userName;
                if($isDomainAdmin){
                    $credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName,$password;
                    $validAccount=$true;
                    }else{
                        $failureMessage += "$attempt out of $maxAttempts`: $userName account is valid, but it is not a Domain Admin";
                        }
                }else{
                    $failureMessage += "$attempt out of $maxAttempts`: username and/or password error";
                    }           
            }catch{
                $failureMessage += "Unable to bind to $env:USERDOMAIN.";
                }

        # Depending on whether there are failures, proceed accordingly       
        if($failureMessage){
        If ($attempt -lt $maxAttempts-1) {
            $message = "$failureMessage`: Authentication error. Please Try again.";
            Write-Warning $message;
            } elseif ($attempt -eq $maxAttempts-1){
                $message = "$failureMessage`: Last attempt.";
                Write-Warning $message;
                $credentials= $false;
                }
            }

        } Until (($ValidAccount) -or ($Attempt -eq $MaxAttempts))
    if($credentials){return $credentials;}else{return $false}
}
function setScheduledTasks{
    param(
        [string[]]$computernames,
        [string]$scriptFile,
        [string]$description,
        [string]$repeatIntervalMinutes,
        [string]$taskName,
        [System.Management.Automation.PSCredential]$credential
        )
    function getUncServer($path){
        $uncServer=$scriptFile | select-string -pattern "(?<=\\\\)(.*?)(?=\\)" | Select -ExpandProperty Matches | Select -ExpandProperty Value
        $isValidPath=test-path $scriptfile -ErrorAction SilentlyContinue
        if ($uncServer -and $isValidPath){
            return $uncServer;
        }else{
            return $False;
            }
    }

    $username=$credential.Username;
    $plaintextPassword=$credential.GetNetworkCredential().password;
    $uncServer=getUncServer $scriptFile
    if ($credential){
        foreach ($computer in $computernames){
            write-host "Adding task on $computer..."
            if($session.state -eq 'Opened'){remove-pssession $session}
            do{
                $session=if($credential){
                        try{
                            New-PSSession -ComputerName $computer -Credential $credential -ea Stop
                        }catch{
                            New-PSSession -ComputerName $computer -Credential $credential -SessionOption $(new-pssessionoption -IncludePortInSPN)
                        }
                    }else{
                        try{
                            New-PSSession -ComputerName $computer -ea Stop
                        }catch{
                            New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
                        }
                    }
            } until ($session.state -match "Opened")
            Invoke-Command -session $session -ScriptBlock{
                param($scriptFile,$taskName,$description,$repeatMinutes,$user,$password,$uncServer)          
                $originalScriptPath=$scriptFile
                $windowsVersion=[Environment]::OSVersion.Version
                if($windowsVersion -gt [version]'6.1'){
                    $username="$env:USERDOMAIN`\$user"
                    $encryptedPassword=ConvertTo-SecureString $password -AsPlainText -Force
                    $adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$encryptedPassword;
                    $destinationPath='C:\Scripts\'
                    $null=mkdir $destinationPath -force
                    $fileName=split-path $scriptfile -leaf
                    $sourceDirectory=split-path $scriptfile -parent

                    # Unrestrict this Domain Administrator from security prompts
                    Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted -Force
                    
                    try{                        
                        $unavailableDriveLetters=(Get-WmiObject -Class Win32_Volume).DriveLetter|sort
                        $availableDriveLetters=.{(65..90|%{[char]$_})|?{"$_`:" -notin $unavailableDriveLetters}}
                        [char]$firstAvailableDriveLetter=$availableDriveLetters[0]
                        $scriptFile=$destinationPath+$fileName
                        $null=New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem -Root $sourceDirectory -Persist -credential $adminCredential
                        $command="Copy-Item '$firstAvailableDriveLetter`:\$fileName' '$destinationPath' -force"
                        $fileAlreadyAvailable=.{
                            try{
                                $x=(get-item $scriptFile -ea SilentlyContinue).LastWriteTime
                                $y=(get-item "$firstAvailableDriveLetter`:\$fileName" -ea SilentlyContinue).LastWriteTime
                                return $x -eq $y
                            }catch{
                                return $false
                            }
                        }
                        if($fileAlreadyAvailable){
                            write-host "$scriptFile is already available at the destination."
                        }else{
                            write-host $command
                            invoke-expression $command
                        }                        
                        Remove-PSDrive $firstAvailableDriveLetter
                    }catch{
                        write-warning $_
                        return $false
                    }
                    
                    # Unblock file & Ensure that script exists
                    <# Overcome error caused by double hop issue:
                    Cannot find path '\\snapshots\FileServerClusters\Daily-VSS-Snapshot.ps1' because it does not exist.
                    + CategoryInfo          : ObjectNotFound: (\\snapshots\Fil...SS-Snapshot.ps1:String) [Unblock-File], ItemNotFoundException
                    + FullyQualifiedErrorId : FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand
                    + PSComputerName        : SHERVER007

                    1. Run scheduled task as: New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $scriptFile"
                    2. Unblock-File -Path $scriptFile
                    #> 
                    if($uncServer -and $originalScriptPath -eq $scriptFile){
                        $unblockFile=Invoke-Command -computername $uncServer -Credential $adminCredential -ScriptBlock{
                                Unblock-File -Path $Args[0];Test-Path $Args[0] -ErrorAction SilentlyContinue} -Args $scriptFile  
                        if ($unblockFile -eq $False) {
                            write-warning "Errors locating $scriptFile... Aborting execution on $env:computername"
                            }
                    }else{
                        $unblockFile=.{Unblock-File -Path $scriptFile;Test-Path $scriptFile -ErrorAction SilentlyContinue}
                        if ($unblockFile -eq $False) {
                            write-warning "Errors locating $scriptFile... Aborting execution on $env:computername"
                            }
                    }

                    $settingsCommand = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit 0
                    $callPowerShell = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $scriptFile"
                    $taskTrigger =  New-ScheduledTaskTrigger `
                                    -Once `
                                    -At (Get-Date) `
                                    -RepetitionInterval (New-TimeSpan -Minutes $repeatMinutes)
                    # Unregister the Scheduled task if it already exists
                    Get-ScheduledTask $taskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false;

                    # Create new scheduled task
                    Register-ScheduledTask -Action $callPowerShell -Trigger $taskTrigger -TaskName $taskName -Description $description -User $username -Password $password -Settings $settingsCommand -RunLevel Highest;
                }else{
                    write-host "$env:computername is too old. Just turn it off."
                }
                } -ArgumentList $scriptFile,$taskName,$description,$repeatIntervalMinutes,$username,$plainTextPassword,$uncServer
                if($session){Remove-PSSession $session}
                pause;
            }        
    }else{
        write-host "Please run this program with a valid Administrator account."
        }
}

$adminCredential=obtainDomainAdminCredentials
setScheduledTasks -computernames $computers -scriptFile $scriptFile -description $description `
    -repeatIntervalMinutes $repeatIntervalMinutes -taskName $taskName -credential $adminCredential