Category Archives: Powershell

Scheduled Powershell Scripts without storing credentials

Sometimes I want to schedule a script to run with specific domain credentials without storing anything blatantly risky. For example here I wanted to schedule maintenance notifications to users that have been logged in for so long that their hosts are up for replacement…

Easiest way is to set up a GMSA and use it for the scheduled tasks, the only caveat is that you can’t select a G/MSA account for those tasks in the task scheduler UI. Hmm.

The workaround is to set the task account from either SC or PowerShell; however I also wanted to script the rest of the task setup. Nobody likes instructions that involve “Do a dozen things by hand in the UI, then write some lines to modify it”.

The next step to low footprint bliss would be to say goodbye to all the files and ACLs, let’s just inline the scripts (if they’re short enough)! So here’s my script to create a file-less, credential-less (kind of) PowerShell scheduled task that in this case schedules desktop messages for Citrix sessions. Ironically this specific example drops transcript copies but you get the point.

#Encode script as Base64, send a Citrix message in this example
function EncodeMessageTaskScript ($MessageText, $AdminAddress)
{
    $TaskScript = @"
    Start-Transcript "C:\ScriptLogs\ScheduledPS.log" -Append
    & { Add-Pssnapin @('Citrix.Host.Admin.V2′,'Citrix.Broker.Admin.V2′)}

    `$CurrentSessions = Get-BrokerSession -AdminAddress "$AdminAddress" -MaxRecordCount 1000 | ? DesktopGroupName -eq 'Nope'
    `$CurrentSessions | % { Send-BrokerSessionMessage -AdminAddress "$AdminAddress" -InputObject `$_ -MessageStyle Critical -Text "$MessageText"`nMessage Sent [`$(Get-Date)]" -Title "Maintenance Warning"}

    Stop-Transcript

"@#Let's pretend that this is indented and that you can't end a herestring with whitespace...

    [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($TaskScript))
}

function CreateScheduledGMSATask ($EncodedPsScript, [datetime]$TriggerDateTime, $TaskName)
{

    $Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-EncodedCommand `"$EncodedPsScript`" -NoLogo -NoProfile -ExecutionPolicy Bypass"
    $Principal = New-ScheduledTaskPrincipal -LogonType Password -RunLevel Limited -UserId 'DOMAIN\[G]MSA$'
    $Settings = New-ScheduledTaskSettingsSet -Compatibility Win8
    $Trigger = New-ScheduledTaskTrigger -Once -At $TriggerDateTime

    $TaskObj = New-ScheduledTask -Action $Action -Principal $Principal -Trigger $Trigger -Settings $Settings
    Register-ScheduledTask -TaskName $TaskName -InputObject $TaskObj

}

$EncodedReminderTask = EncodeMessageTaskScript -MessageText "Your Message Text" -AdminAddress $CitrixAdminAddress
CreateScheduledGMSATask -EncodedPsScript $EncodedReminderTask -TriggerDateTime $MaintReminder -TaskName "MessageTask $($MaintStartTime.ToString('yyyyMMdd.HHmmss'))"

Remote bulk fix for VSS LLDP CAPI 513 error.

I’m a stickler for keeping error logs clean where possible. I wanted to fix the VSS CAPI 513 error (https://support.microsoft.com/en-ca/help/3209092) on my DPM protected servers; however, I’m also lazy efficient and didn’t want to do it manually. Here’s my quick and dirty powershell function to apply the fix to all of the appropriate servers.

Automation is a fantastic way to break things with unprecedented speed. Scripts should be understood before running. Also all the error decorations aren’t necessary, but who’s to say I can’t have fun with a blog post?
Caveat Emptor.

function Repair-mslldpPermissions {

    param (

        [string]$TargetComputer

    )

 

    $mslldpSDDL = Invoke-Command -ComputerName $TargetComputer -ScriptBlock {sc.exe sdshow mslldp}

    $ntserviceSecString = ‘(A;;CCLCSWLOCRRC;;;SU)’

 

    if ($mslldpSDDL -match $ntserviceSecString) {

        Write-Warning “mslldp service already has NT Service permission fix applied on $TargetComputer!”

        return;

    }

 

    if ($mslldpSDDL -match “[OGS]:”) {

        Write-Error “I’m not smart enough to understand the SDDL on $TargetComputer.

        I expect the SDDL for this service to match the default, which only contains dacl flags.

        Make me smarter if you want to continue!” -Category InvalidOperation

    }

 

    $newSDDL = $mslldpSDDL$ntserviceSecString

    $output = Invoke-Command -ComputerName $TargetComputer -ScriptBlock {$sddl = $args[0]; sc.exe sdset mslldp $sddl} -ArgumentList $newSDDL

 

    switch -Wildcard ($output) {

        “*5*” {

            Write-Error “Insufficient permissions to alter SDDL of mslldp service. Failed to set SDDL” -Category PermissionDenied

            return;

        }

        “*SetServiceObjectSecurity SUCCESS*” {

            Write-Host “Successfully updated mslldp service SDDL”

            return;

        }

        Default {

            Write-Error “sc returned unexpected result:`n$output -RecommendedAction “RTError” -Category InvalidResult

            return;

        }

    }

 

}

 

PowerShell Inline Status Updates Without Write-Progress

Preface: Don’t use this, it’s really bad practice for PowerShell code to work by manipulating the host output stream. It’ll work, but it’ll mess with logging, transcripts, text pipeline (ew, gross, use objects), and other things I’m sure. For a one off that you’re writing in a busy remote shell… maybe...

Here’s a lazy way of having a progress indicator that doesn’t fill the whole screen history and doesn’t require Write-Progress. Use NoNewline to avoid implicit newline/carriage return, then manually add a carriage return (`r) at the start of your line to overwrite existing text.

The magic “$([char]27[2K`r” (Escape[2K) is an ANSI control sequence that tells the terminal to preemptively erase the entire line so that you don’t end up with leftover characters if your next value is shorter than the previous one. https://www.real-world-systems.com/docs/ANSIcode.html#CSI

Write-Host "`r$TimestampOrWhatever: $ValueOrWhatever" -NoNewline

#or

Write-Host "$([char]27)[2K`r" -NoNewline #Equivalent to Esc[2K
Write-Host "$TimestampOrWhatever: $ValueOrWhatever" -NoNewline