Flow control in script blocks when invoking PowerShell

Hello,

I am invoking a complex expression as per PowerShell Team blog - invoking-powershell-with-complex-expressions-using-scriptblocks/ and flow control statements seem to be ignored/not executed.

In this application a program is using the .Net System.Diagnostics.Process class to invoke PowerShell with redirected I/O. The script sent to PowerShell via standard input creates a ScriptBlock variable, encodes it in base 64 and launches a PowerShell as an elevated process.

In both the “outer script” and the “inner” scriptblock, flow control statements don’t appear to execute, although all single line PowerShell statements do execute. While I am able to achieve my objective the inability to have flow control weakens the robustness of the code. Please see the code below.

Can anyone suggest why flow control statement don’t execute?

### PowerShell Script Service Install
$innerScriptBlock = { 
	$scriptOutput = @{}
	$outputFileName = "tempFileName.xml"
	$serviceName = "SomeWindowsService"
	$serviceDisplay = "Some Windows Service"
	$serviceDescription = "Not some other service"
	$servicePath = "c:\bin\someservice.exe"
	$serviceAccount = "MachineName\DomainServiceAccount"
	$serviceStartupType = "Manual"
	$machineName = "MachineName"

	$sspw =  ConvertTo-SecureString "ignoreThisPassword" -asPlainText -Force

	if ($serviceAccount -eq "LocalSystem") 
	{
		$serviceCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "$machineName\LocalSystem",$sspw
	}
	else
	{
		$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Service Credentials" 
		if ($? -eq $false ) 
		{
			$outLog += "Get Creds failed`n"
		}
	}

	$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 
	if ($? -eq $false ) 
	{
		$outLog += "Service failed`n"
	}
	else
	{
		$outLog += "Service created:`n "
	}
	$scriptOutput["OutputLog"] = $outLog
	$scriptOutput | ConvertTo-Xml -as string | Set-Content -Path $outputFileName
	#### end inner script block
}

$bytes = [System.Text.Encoding]::Unicode.GetBytes($innerScriptBlock)
$innerBlockEncoded = [Convert]::ToBase64String($bytes)
$p = New-Object -TypeName System.Diagnostics.Process
$p.StartInfo.FileName = "PowerShell.exe"
$p.StartInfo.WorkingDirectory = "c:\MyWorkingDir"
$p.StartInfo.Verb = "runas"
$p.StartInfo.UseShellExecute = $true
##$p.StartInfo.WindowStyle = 1
$p.StartInfo.Arguments = "-EncodedCommand $innerBlockEncoded"
$ret_value = $p.Start()
$ret_value = $p.WaitForExit(120)

$t = 0
while (( $t -le 10000) -and (-not (Test-Path $outputFileName)) ) 
{
	Start-Sleep -Milliseconds 100
	$t += 100
}

$script_out = Get-Content -Path $outputFileName -force
$script_out | Write-Output
Remove-Item -Path $outputFileName -force

Thanks for your time and consideration.

PS - reposted this after making (pretty code) edits and the forum deleted the post.

April,

with a quick overlook I see some minor issues …

on line 15 you are actually comparing “MachineName\DomainServiceAccount” to “LocalSystem”. That’s always gonna be false. :wink:

on line 22 and 29 you check if the last command completed successfully. As both these command are variable assignments they’re likely always be successful. And btw: because $? returns a boolean you could write it this way: “if ( -not $?)

… I did not pay close attention yet but when you work with scriptblocks and variables you have to be careful with the scopes and the variables. Outside variables are not available inside a scriptblock by default. :wink:

Not sure what’s the use case here. To install a service, just invoke New-Service.

Hi Olaf,

Thank you for your response.

The variables are a copy & paste error into the forum post. I have a templated script that uses variable substitution at runtime to fill in variable values. I wasn’t thinking when I pasted $outputFileName into the outer script block logic.

with regards to the expression: $serviceAccount -eq “LocalSystem”
and the initialization: $serviceAccount = “MachineName\DomainServiceAccount”
you are correct! this is another case of a bad example as the $serviceAccount variable is provided by an outside source.

what is interesting (frustrating) is that, using the code provided in this post, the snippet:

	$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Service Credentials" 
	if ($? -eq $false ) 
	{
		$outLog += "Get Creds failed`n"
	}

never executes and instead the prompt for credentials (always) occurs when executing:

$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 

The code shown reeks of desperation in an attempt to use the old method of writing messages to a “log” since I can’t easily perform real-time debugging within the execution context:
BizApp -> invoke hidden powershell process with redirected I/O -> invoke powershell elevated.

using redirected input avoids the need to set Execution policies for scripting (the intention is to use this in third party environments where there will be a mix of policies as we move from environment to environment (resulting from different IT practices)).

Thanks for your time & consideration.
Kind regards, April

Hi,
we are executing from a BizApp where we are automating installation and configuration of different components needed for the BizApp.

The approach outlined creates a model for executing (dynamic) PowerShell scripts from the application with the ability to elevate when needed.

The Scripts work in PowerShell IDE or executed from PowerShell command line.

HTH

Thanks for your response.

$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Service Credentials" 
if ($? -eq $false ) 
{
    $outLog += "Get Creds failed`n"
}

I’m still confused. First you start to fill in a varaiable by calling Get-Credential. Of course it prompts for credentials … you told it to. :wink:
Then you check if this variable assignment completed successfully. As long as you enter something in the credentials prompt it always will. So the condition you use will likely never be true.

Hi Olaf,

Sorry for the confusion. You are correct the code snippet I provided has logic “dead-ends”. My mistake.

Also important note - this is a PowerShell 4.0 system (on Win 2012 R2)

The snippet is overly contrived. As a templated script the account is inserted into the template just prior to execution. The value could be: LocalSystem or a Domain service account. The desire is to: 1) if it is “LocalSystem” generate credentials and no need to prompt the user (in the BizApp). If the account specified is not recognized as “LocalSystem” then prompt the user for the domain service account credentials. Unfortunately the if-the-else logic does execute at all (the core of the issue) based on diagnostic inspection.

Case 1 - Local System Account - there should be no prompt for credentials, but a prompt does occur when the New-Service line is reached. There is no evidence that $serviceCredentials was populated via the if statement (if ($serviceAccount -eq “LocalSystem”) )

$serviceAccount = "LocalSystem"
$serviceStartupType = "Manual"
$serviceName = "SomeWindowsService"
$serviceDisplay = "Some Windows Service"
$serviceDescription = "Not some other service"
$servicePath = "c:\bin\someservice.exe"
$machineName = "MachineName"
$sspw =  ConvertTo-SecureString "ignoreThisPassword" -asPlainText -Force

if ($serviceAccount -eq "LocalSystem") 
{
	$serviceCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "$machineName\LocalSystem",$sspw
}
else
{
	$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Service Credentials" 
	if ($? -eq $false ) 
	{
		$outLog += "Get Creds failed`n"
	}
}
$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 

</pre>


Case 2 - Domain Service Account - there should be a prompt for credentials with the Prompt Message: Enter Domain Service Account Service Credentials, but this prompt does not occur and there is no evidence that $serviceCredentials was populated via the else clause of the if statement: if ($serviceAccount -eq "LocalSystem") 

Instead we get a prompt for credentials at the New-Service line


<pre>
$serviceAccount = "MyDomainServiceAccount"
$serviceStartupType = "Manual"
$serviceName = "SomeWindowsService"
$serviceDisplay = "Some Windows Service"
$serviceDescription = "Not some other service"
$servicePath = "c:\bin\someservice.exe"
$machineName = "MachineName"
$sspw =  ConvertTo-SecureString "ignoreThisPassword" -asPlainText -Force

if ($serviceAccount -eq "LocalSystem") 
{
	$serviceCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "$machineName\LocalSystem",$sspw
}
else
{
	$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Domain Service Account Service Credentials" 
	if ($? -eq $false ) 
	{
		$outLog += "Get Creds failed`n"
	}
}

$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 

Everything I have done indicates that if-then-else statements are not executing in this execution context. I can take the contents of the $innerScriptBlock and type them line by line at the Powershell command line or as a script in the IDE and they work as expected.

Thanks for your time & consideration.

second try to post with correct pre tags

Hi Olaf,

Sorry for the confusion. You are correct the code snippet I provided has logic “dead-ends”. My mistake.

Also important note - this is a PowerShell 4.0 system (on Win 2012 R2)

The snippet is overly contrived. As a templated script the account is inserted into the template just prior to execution. The value could be: LocalSystem or a Domain service account. The desire is to: 1) if it is “LocalSystem” generate credentials and no need to prompt the user (in the BizApp). If the account specified is not recognized as “LocalSystem” then prompt the user for the domain service account credentials. Unfortunately the if-the-else logic does execute at all (the core of the issue) based on diagnostic inspection.

Case 1 - Local System Account - there should be no prompt for credentials, but a prompt does occur when the New-Service line is reached. There is no evidence that $serviceCredentials was populated via the if statement

$serviceAccount = "LocalSystem"
$serviceStartupType = "Manual"
$serviceName = "SomeWindowsService"
$serviceDisplay = "Some Windows Service"
$serviceDescription = "Not some other service"
$servicePath = "c:\bin\someservice.exe"
$machineName = "MachineName"
$sspw =  ConvertTo-SecureString "ignoreThisPassword" -asPlainText -Force

if ($serviceAccount -eq "LocalSystem") 
{
	$serviceCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "$machineName\LocalSystem",$sspw
}
else
{
	$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Service Credentials" 
	if ($? -eq $false ) 
	{
		$outLog += "Get Creds failed`n"
	}
}
$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 

</pre>

Case 2 - Domain Service Account - there should be a prompt for credentials with the Prompt Message: Enter Domain Service Account Service Credentials, but this prompt does not occur and there is no evidence that $serviceCredentials was populated via the else clause of the if statement.

<pre>

$serviceAccount = "MyDomainServiceAccount"
$serviceStartupType = "Manual"
$serviceName = "SomeWindowsService"
$serviceDisplay = "Some Windows Service"
$serviceDescription = "Not some other service"
$servicePath = "c:\bin\someservice.exe"
$machineName = "MachineName"
$sspw =  ConvertTo-SecureString "ignoreThisPassword" -asPlainText -Force

if ($serviceAccount -eq "LocalSystem") 
{
	$serviceCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "$machineName\LocalSystem",$sspw
}
else
{
	$serviceCredentials = Get-Credential -UserName $serviceAccount -Message "Enter Domain Service Account Service Credentials" 
	if ($? -eq $false ) 
	{
		$outLog += "Get Creds failed`n"
	}
}

$catchOutput = New-Service -Name $serviceName -BinaryPathName $servicePath -StartupType $serviceStartupType -Credential $serviceCredentials -DisplayName $serviceDisplay -Description $serviceDescription 

Everything I have done indicates that if-then-else statements are not executing in this execution context. I can take the contents of the $innerScriptBlock and type them line by line at the Powershell command line or as a script in the IDE and they work as expected.

Thanks for your time & consideration.

I recommend to implement error handling and/or logging in your script to figure out what doesn’t work as expected.

Works for me. Cool idea.

$script = { $a = 1; if ($a -eq 1) { "it's one" } else { "it's not one" } }

[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))

IAAkAGEAIAA9ACAAMQA7ACAAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAGUAbABzAGUAIAB7ACAAIgBpAHQAJwBzACAAbgBvAHQAIABvAG4AZQAiACAAfQAgAA==

pwsh -encodedcommand IAAkAGEAIAA9ACAAMQA7ACAAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAGUAbABzAGUAIAB7ACAAIgBpAHQAJwBzACAAbgBvAHQAIABvAG4AZQAiACAAfQAgAA==

it's one

Hi

@Olaf - the $outLog messages are capturing execution of different steps. I have tried lots of variations and they all indicate that if-then-else wasn’t executed.

@js - glad that is worked - it at least demonstrates that it should work! I will try using the semi-colon delimiters and compress the flow-control to a single line.

There may be subtleties between use of command line parameters (?). For example when using redirected I/O does: “-Command -” differ from “-File -”? is there a difference? (IDK)

The original blog post only shows example using single line commands. My script blocks show that all single line commands within the scriptblock work, but multi-line flow-control lines doesn’t (hence maybe compress to a single line and use ‘;’ line delimiter as @js demonstrated). Observations also indicate that the outer script via standard input is also not executing the while loop.

Thanks to all for your input!

Multilines shouldn’t matter:

$script = { 
  $a = 1
  if ($a -eq 1) { 
    "it's one" } 
  else { 
    "it's not one"
  } 
} 

[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))   
                              IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

pwsh -encodedcommand IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

it's one

Hi @js,

“Multilines shouldn’t matter” - I would hope so!!!

What version of PowerShell are you using? My test system is: Version 4.0

thanks!

and launched from a 3rd party BizApp via a .Net Process class using redirected I/O.

Ugh. Where did my last answer go? I tried it in powershell 7 and 5.

Multilines shouldn’t matter:

$script = {
$a = 1
if ($a -eq 1) {
  "it's one" }
else {
  "it's not one"
}
}

[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
                              IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

pwsh -encodedcommand IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

it's one

@js - thanks!

Weired. My post keeps disappearing. Works for me in powershell 5 and 7.

Multilines shouldn’t matter:

$script = {
$a = 1
if ($a -eq 1) {
  "it's one" }
else {
  "it's not one"
}
}

[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
                              IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

pwsh -encodedcommand IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

it's one

Weird. My posts keep disappearing. Works for me in powershell 5 and 7. I’ll let the encoded lines stay broken. Otherwise the forum deletes it.

$script = {
$a = 1
if ($a -eq 1) {
  "it's one" }
else {
  "it's not one"
}
}

[System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
                              IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQA\
gAAoAZQBsAHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

pwsh -encodedcommand IAAKACQAYQAgAD0AIAAxAAoAaQBmACAAKAAkAGEAIAAtAGUAcQAgADEAKQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABvAG4AZQAiACAAfQAgAAoAZQBs\
AHMAZQAgAHsAIAAKACAAIAAiAGkAdAAnAHMAIABuAG8AdAAgAG8AbgBlACIACgB9ACAACgA=

it's one

I suppose this could be a security concern. If you gave that encoded command to someone else, they wouldn’t know what they were running…