0

I need to start a Powershell script from a .bat file and the Powershell script accepts parameters. Today, I run the script from a single line, like this:

cmd.exe /C pwsh.exe -Command "& {C:\temp\test.ps1 -ForceFullSync -AzureUserFilterOverride { $users | Where-Object { ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com') } } -CompanyId bb8083d14755f910291fe0e8036d43ea -ManageObjects Contact -AzureUserPropertyList @(@{ msProp = 'accountEnabled'; mappedProp = 'active' }, @{ msProp = 'businessPhones'; mappedProp = 'phone' }, @{ msProp = 'givenName'; mappedProp = 'first_name' }, @{ msProp = 'mail'; mappedProp = 'email' }, @{ msProp = 'mobilePhone'; mappedProp = 'mobile_phone' }, @{ msProp = 'onPremisesImmutableId'; mappedProp = 'u_correlation_id' }, @{ msProp = 'surname'; mappedProp = 'last_name' }, @{ msProp = 'userPrincipalName'; mappedProp = 'user_name' }, @{ msProp = 'assignedLicenses'; mappedProp = '' }) -SecretId 120953 -AzureAdTenantDomain domain.onmicrosoft.com -ServiceNowBaseUri 'https://testinstance.service-now.com' -IdentityProviderSysId '5ffa40e4975a391077dd7a76f053afbf' -LogPath C:\temp\domain\log.txt -Verbose; Exit $LASTEXITCODE }"

That works fine, but is difficult to read (and will be getting longer with additional parameters), so I would like to break it by line. I tried the following:

cmd.exe /C pwsh.exe -Command "& {
    C:\temp\test.ps1 `
    -ForceFullSync `
    -AzureUserFilterOverride { `$users | Where-Object { ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com') } } `
    -CompanyId bb8083d14755f910291fe0e8036d43ea `
    -ManageObjects Contact `
    -AzureUserPropertyList @(
        @{ msProp = 'accountEnabled'; mappedProp = 'active' },
        @{ msProp = 'businessPhones'; mappedProp = 'phone' },
        @{ msProp = 'givenName'; mappedProp = 'first_name' },
        @{ msProp = 'mail'; mappedProp = 'email' },
        @{ msProp = 'mobilePhone'; mappedProp = 'mobile_phone' },
        @{ msProp = 'onPremisesImmutableId'; mappedProp = 'u_correlation_id' },
        @{ msProp = 'surname'; mappedProp = 'last_name' },
        @{ msProp = 'userPrincipalName'; mappedProp = 'user_name' },
        @{ msProp = 'assignedLicenses'; mappedProp = '' }
    ) `
    -SecretId 120953 `
    -AzureAdTenantDomain domain.onmicrosoft.com `
    -ServiceNowBaseUri 'https://testinstance.service-now.com' `
    -IdentityProviderSysId '5ffa40e4975a391077dd7a76f053afbf' `
    -LogPath C:\temp\domain\log.txt `
    -Verbose; `
    Exit `$LASTEXITCODE
}"

But when I run the .bat file, all I get is Notepad opening c:\temp\test.ps1. When I close it, the command shell shows me:

c:\temp>test.bat

c:\temp>cmd.exe /C pwsh.exe -Command "& {C:\temp\test.ps1 `

c:\temp>-ForceFullSync ` '-ForceFullSync' is not recognized as an internal or external command, operable program or batch file.

c:\temp>-AzureUserFilterOverride { $users | Where-Object { ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com') } } '-AzureUserFilterOverride' is not recognized as an internal or external command, operable program or batch file.

c:\temp>

Based on Using multi-line PowerShell commands from cmd.exe I tried replacing the line-ending backticks with the caret character like this:

cmd.exe /C pwsh.exe -Command "& {
    C:\temp\test.ps1 ^
    -ForceFullSync ^
    -AzureUserFilterOverride { `$users | Where-Object { ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com') } } ^
    -CompanyId bb8083d14755f910291fe0e8036d43ea ^
    -ManageObjects Contact ^
    -AzureUserPropertyList @(
        @{ msProp = 'accountEnabled'; mappedProp = 'active' },
        @{ msProp = 'businessPhones'; mappedProp = 'phone' },
        @{ msProp = 'givenName'; mappedProp = 'first_name' },
        @{ msProp = 'mail'; mappedProp = 'email' },
        @{ msProp = 'mobilePhone'; mappedProp = 'mobile_phone' },
        @{ msProp = 'onPremisesImmutableId'; mappedProp = 'u_correlation_id' },
        @{ msProp = 'surname'; mappedProp = 'last_name' },
        @{ msProp = 'userPrincipalName'; mappedProp = 'user_name' },
        @{ msProp = 'assignedLicenses'; mappedProp = '' }
    ) ^
    -SecretId 120953 ^
    -AzureAdTenantDomain domain.onmicrosoft.com ^
    -ServiceNowBaseUri 'https://testinstance.service-now.com' ^
    -IdentityProviderSysId '5ffa40e4975a391077dd7a76f053afbf' ^
    -LogPath C:\temp\domain\log.txt ^
    -Verbose; ^
    Exit `$LASTEXITCODE
}"

But then the shell said,

c:\temp>test.bat

c:\temp>cmd.exe /C pwsh.exe -Command "& {

c:\temp>C:\temp\test.ps1 -ForceFullSync
-AzureUserFilterOverride { `$users | Where-Object { ($.assignedLicenses.Count -ge 1) -and ($.userPrincipalName -match 'domain.com') } } -CompanyId bb8083d14755f910291fe0e8036d43ea
-ManageObjects Contact -AzureUserPropertyList @( 'Where-Object' is not recognized as an internal or external command, operable program or batch file.

c:\temp>

I feel like I must be close, but am missing something small.

8
  • If you are running the command from within a batch file, that is already being executed by cmd.exe, so preceding your pwsh.exe command with cmd.exe /C seems completely redundant, and potentially problematic. Effectively sending everything as arguments, (to be first passed to another instance of cmd.exe), means that a round of parsing by that interpreter takes place before its resulting strings are then passed to pwsh.exe. I would strongly therefore advise that you begin by removing the first eleven characters from your command. You should also be using -File instead of -Command. Commented Jan 19, 2024 at 19:25
  • You need to use the full path of the pwsh.exe which is in the folder C:\Windows\System32\WindowsPowerShell\v1.0. Since all the cmd.exe commands are in powershell I would use an extension PS1 and run the bat as a powershell script. Commented Jan 19, 2024 at 19:27
  • The line continuation character in cmd.exe is a CIRCUMFLEX ACCENT ^ and not a GRAVE ACCENT ` as it is in pwsh.exe. How about putting the PowerShell code into an Invoke-test.ps1 script as @Compo has suggested? Commented Jan 19, 2024 at 19:46
  • What problem are you solving by trying to do this using cmd.exe? Commented Jan 19, 2024 at 21:44
  • @Bill_Stewart, In the batch file, I will concede that cmd.exe is likely redundant. I should be fine to drop it, but I still need to launch the Powershell script from a .bat file because of the method by which we monitor scheduled tasks. I tried using the answer Compo suggested, but the multi-line parameters are still causing problems without cmd.exe /c. Commented Jan 19, 2024 at 22:11

2 Answers 2

0
  • In a batch file, there's no reason to call an external program such as powershell.exe via cmd.exe /C - just call that program directly.

  • There's no reason to use "& { ... }" in order to invoke code passed to PowerShell's CLI via the -Command (-c) parameter[1] - just use "..." directly - but see below re multiline string use from cmd.exe

  • The answer you linked to in your question spells out the relevant rules re multiline PowerShell CLI calls from cmd.exe, and these are the ones you didn't follow:

    • cmd.exe has no concept of multiline "..." strings, so your code cannot work.

      • You may use per-line "..." enclosure - inside of which you needn't worry about escaping cmd.exe metacharacters such as & and | - as long as each such line starts with whitespace and is terminated by an unquoted ^ for line continuation (for cmd.exe's benefit).
    • Every line in a multiline statement must be terminated with ^ - irrespective of whether a given block of lines happens to form a single PowerShell construct that in PowerShell doesn't need line continuations.

    • Additionally (these are things that your attempt does observe):

      • Only the -Command CLI parameter (which is the default in powershell.exe, the Windows PowerShell CLI) - not the -File parameter (which is the default in pwsh, the PowerShell (Core) CLI) - accepts arbitrary PowerShell code to execute, which is needed in your case.

      • The end of each PowerShell statement must explicitly signaled via ;, given that cmd.exe's line continuations (^) do not introduce newlines.

For a complete overview of the rules for multiline PowerShell CLI calls from cmd.exe - including additional rules that must be observed in the context of for /f - see this answer.


[1] Older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected. Unfortunately, this anti-pattern is still quite prevalent.

Sign up to request clarification or add additional context in comments.

Comments

0

My first instinct would be to try something like this:

@pwsh.exe -File "C:\temp\test.ps1"^
 -ForceFullSync^
 -AzureUserFilterOverride {^
   $users ^| Where-Object {^
    ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com')^
   }^
  }^
 -CompanyId bb8083d14755f910291fe0e8036d43ea^
 -ManageObjects Contact^
 -AzureUserPropertyList @(^
   @{ msProp = 'accountEnabled'; mappedProp = 'active' },^
   @{ msProp = 'businessPhones'; mappedProp = 'phone' },^
   @{ msProp = 'givenName'; mappedProp = 'first_name' },^
   @{ msProp = 'mail'; mappedProp = 'email' },^
   @{ msProp = 'mobilePhone'; mappedProp = 'mobile_phone' },^
   @{ msProp = 'onPremisesImmutableId'; mappedProp = 'u_correlation_id' },^
   @{ msProp = 'surname'; mappedProp = 'last_name' },^
   @{ msProp = 'userPrincipalName'; mappedProp = 'user_name' },^
   @{ msProp = 'assignedLicenses'; mappedProp = '' }^
  )^
 -SecretId 120953^
 -AzureAdTenantDomain domain.onmicrosoft.com^
 -ServiceNowBaseUri 'https://testinstance.service-now.com'^
 -IdentityProviderSysId '5ffa40e4975a391077dd7a76f053afbf'^
 -LogPath 'C:\temp\domain\log.txt'^
 -Verbose

I removed the Exit with $LASTEXITCODE command, because that should already exist within the test.ps1 script.


[Edit /]

If -File does not work for you, you could try it like this using -Command, as per your original submission, instead:

@pwsh.exe -Command "C:\temp\test.ps1"^
 " -ForceFullSync"^
 " -AzureUserFilterOverride {"^
 "   $users | Where-Object {"^
 "    ($_.assignedLicenses.Count -ge 1) -and ($_.userPrincipalName -match 'domain\.com')"^
 "   }"^
 "  }"^
 " -CompanyId bb8083d14755f910291fe0e8036d43ea"^
 " -ManageObjects Contact"^
 " -AzureUserPropertyList @("^
 "   @{ msProp = 'accountEnabled'; mappedProp = 'active' },"^
 "   @{ msProp = 'businessPhones'; mappedProp = 'phone' },"^
 "   @{ msProp = 'givenName'; mappedProp = 'first_name' },"^
 "   @{ msProp = 'mail'; mappedProp = 'email' },"^
 "   @{ msProp = 'mobilePhone'; mappedProp = 'mobile_phone' },"^
 "   @{ msProp = 'onPremisesImmutableId'; mappedProp = 'u_correlation_id' },"^
 "   @{ msProp = 'surname'; mappedProp = 'last_name' },"^
 "   @{ msProp = 'userPrincipalName'; mappedProp = 'user_name' },"^
 "   @{ msProp = 'assignedLicenses'; mappedProp = '' }"^
 "  )"^
 " -SecretId 120953"^
 " -AzureAdTenantDomain domain.onmicrosoft.com"^
 " -ServiceNowBaseUri 'https://testinstance.service-now.com'"^
 " -IdentityProviderSysId '5ffa40e4975a391077dd7a76f053afbf'"^
 " -LogPath C:\temp\domain\log.txt"^
 " -Verbose;"^
 " Exit $LASTEXITCODE"

4 Comments

Tried this in my .bat file, but it says, "'Where-Object' is not recognized as an internal or external command, operable program or batch file."
Apologies, forgot to escape the pipe character. Updated code, please try again.
This probably works to prevent cmd.exe's parsing errors, but the nature of the arguments being passed (arrays, hashtables) prevents use of -File and requires -Command. With the latter ; exit $LASTEXITCODE is necessary to pass the script's specific exit code though (otherwise any nonzero one would be reported as 1).
I have made an edit to include an untested -Command version too, @mklement0.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.