Flow control in script blocks when invoking PowerShell

Welcome Forums General PowerShell Q&A Flow control in script blocks when invoking PowerShell

Viewing 18 reply threads
  • Author
    Posts
    • #219309
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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.

    • #219315
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      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. 😉

      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. 😉

    • #219327
      Participant
      Topics: 12
      Replies: 523
      Points: 1,214
      Helping Hand
      Rank: Community Hero

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

    • #219330
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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

    • #219336
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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.

    • #219435
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP
      $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:

      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. 😉
      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.

    • #219543
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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.

      • This reply was modified 1 month, 2 weeks ago by April Daly. Reason: fixing mis rendering of pre tags
    • #219552
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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.

    • #219573
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

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

    • #219576
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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
    • #219591
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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!

    • #219594
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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
      • This reply was modified 1 month, 2 weeks ago by js.
      • This reply was modified 1 month, 2 weeks ago by js.
    • #219606
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      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!

    • #219609
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

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

    • #219612
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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
      • This reply was modified 1 month, 2 weeks ago by js.
      • This reply was modified 1 month, 2 weeks ago by js.
    • #219621
      Participant
      Topics: 5
      Replies: 12
      Points: 72
      Rank: Member

      @js – thanks!

    • #219624
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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
    • #219627
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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
    • #219642
      js
      Participant
      Topics: 29
      Replies: 813
      Points: 2,450
      Helping Hand
      Rank: Community Hero

      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…

Viewing 18 reply threads
  • You must be logged in to reply to this topic.