Author Posts

July 21, 2016 at 7:00 pm

Securing password for use with .RegisterTaskDefinition()

I'm working on a helper function that registers a new task in Task Scheduler. The task is triggered by Event id 400, in the Windows PowerShell event log. The function is working as desired, with one exception- I am unable to secure the password. When passing in a SecureString I get a Type mismatch error. Is there any way I can use a secure credential here? Using a plain text password is not a valid option for me. I was unable to use the PSScheduledJob module, because New-JobTrigger does not seem to support 'When a specific system event occurs' as a trigger yet.

I've tried:

$taskRunasUserPwd = Read-Host 'Enter Your Password: ' –AsSecureString

and

$creds = Get-Credential

Then for the password value passed in:

$creds.Password

Both of these attempts yield this error message:

ERROR: Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
qpo.ps1 (28, 1): ERROR: At Line: 28 char: 1
ERROR: + $rootFolder.RegisterTaskDefinition($name, $TaskDefinition, 6, $taskRu ...
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR:     + CategoryInfo          : OperationStopped: (:) [], COMException
ERROR:     + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
ERROR:

Here is the full function:

#helper function
function Register-CustomTask
{
    param (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$TaskName,
        
        [int]$EventId = 400,
        
        [string]$Subscription = "<QueryList><Query Id='0'><Select Path='Windows PowerShell'>*[System[(EventID='400')]]</Select></Query></QueryList>"
    )
    Set-StrictMode -Version latest
    try
    {
        $Hostname = $Env:computername
        $Service = new-object -ComObject ("Schedule.Service")
        $Service.Connect($Hostname)
        $RootFolder = $Service.GetFolder("\")
        $TaskDefinition = $Service.NewTask(0)
        
        $regInfo = $TaskDefinition.RegistrationInfo
        $regInfo.Description = "$TaskName"
        $regInfo.Author = "$env:USERNAME"
        
        $settings = $taskDefinition.Settings
        $settings.Enabled = $true
        $settings.StartWhenAvailable = $true
        $settings.Hidden = $false
        
        $Triggers = $TaskDefinition.Triggers
        $Trigger = $Triggers.Create(0)
        $Trigger.Id = $EventId
        $Trigger.Subscription = $Subscription
        $Trigger.Enabled = $true
        
        $Action = $TaskDefinition.Actions.Create(0)
        $Action.Path = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
        $Action.Arguments = "Import-Module Foo; New-Foo"
        
        $taskRunAsUser = $env:USERNAME
        #TODO secure password
        $taskRunAsUserPwd = Read-Host 'Enter Your Password: '
        $rootFolder.RegisterTaskDefinition($TaskName, $TaskDefinition, 6, $taskRunAsUser, $taskRunAsUserPwd, 1)
        Clear-Variable -Name taskRunAsUserPwd
    }
    catch { Write-Warning "Could not create task..." }
}

Register-CustomTask -TaskName 'foo'

edit: escaped angle brackets in $Subscription parameter

July 21, 2016 at 7:11 pm

After calling Get-Credential, if you need the plain-text password, you can do $creds.GetNetworkCredential().Password

July 21, 2016 at 7:20 pm

@dave Wyatt Thank you! That worked like a charm.

July 21, 2016 at 8:59 pm

At first I was shocked that the secured password field could be so accessed so easily. I found a great article by Ed Wilson explaining why it was designed this way and why it is not seen as a security issue.

I was also pleased to learn that PSCredential objects created with Import-Clixml do not appear to be subject to this extraction method.

$creds = Import-Clixml -Path C:\temp\MyCredential.xml
$creds.GetNetworkCredential() | fl *


#returns
UserName       : myUserName
Password       : System.Security.SecureString
SecurePassword : System.Security.SecureString
Domain         : 
  • This reply was modified 2 years, 2 months ago by  Trevor Freedland. Reason: spelling

July 21, 2016 at 9:46 pm

SecureStrings aren't there to keep it secret from the user running the script. 🙂 They're there to encrypt the password in memory, so it doesn't show up in dumps if an attacker manages to obtain one.

That said, an encrypted password is useless. You need to be able to convert it back to plain text in some way before you can use it, and if you need to use it in managed code, then it'll just wind up being a string in memory anyway. (Which makes the use of the SecureString sort of pointless, except in PowerShell where you can do things like mask input, encrypt the credential in DSC configs or files, etc.)

July 22, 2016 at 9:49 am

Trevor,

You can store credentials in an XML file, which is quite cool. Plus the password is encrypted.

$CredPath = "$home\Desktop\mycred.xml"
Get-Credential | Export-Clixml -Path $CredPath 

$CredPath = "$home\Desktop\mycred.xml"
$cred = Import-Clixml -Path $CredPath 

July 22, 2016 at 5:05 pm

Thanks Dave for the additional info, this structure makes sense to me now 🙂

Thanks Graham for the climxl note. I have an upcoming project that needs to handle 3rd party credentials in a manner that nobody (including me) has ability to view the password in plain text. I've been investigating clixml as a potential solution to meet that requirement.