How to use foreach loop inside Invoke-Command in PowerShell? - powershell

In the below code I was using $scripts variable to iterate through a foreach loop inside the Invoke-Command statement. But $script values are not replacing properly and outcome seems to be single string as "count.sql size.sql". The foreach loop is executing properly if defined outside the Invoke-Command loop.
Is there any particular way to define foreach loop inside Invoke-Command?
$scripts = #("count.sql", "size.sql")
$user = ""
$Password = ""
$SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
foreach ($server in $servers) {
Invoke-Command -ComputerName $Server -Credential $cred -ScriptBlock {
Param($server, $InputFile, $scripts, $url)
foreach ($script in $scripts) {
echo "$script"
} -ArgumentList "$server,"$scripts","$url"
}

I'm going to assume that the syntax errors in your code are just typos in your question and are not present in your actual code.
The problem you describe has nothing to do with the nested foreach loop. It's caused by the double quotes you put around the arguments you pass to the invoked scriptblock. Putting an array in double quotes mangles the array into a string with the string representations of the values from the array separated by the output field separator defined in the automatic variable $OFS (by default a space). To avoid this behavior don't put variables in double quotes when there is no need to do so.
Change the Invoke-Command statement to something like this:
Invoke-Command -ComputerName $Server -Credential $cred -ScriptBlock {
Param($server, $scripts, $url)
...
} -ArgumentList $server, $scripts, $url
and the problem will disappear.
Alternatively you could use the variables from outside the scriptblock via the using scope modifier:
Invoke-Command -ComputerName $Server -Credential $cred -ScriptBlock {
foreach ($script in $using:scripts) {
echo "$script"
}
}

Related

Problem with test-path on computer from another domain

Im trying to test a remote folder in a computer from another domain with -credential
This command works fine:
Invoke-Command -ComputerName "server" -credential domain\user -ScriptBlock {Test-Path -Path "\\server\s$\temp"}
But if i use it in a script fails:
$servers = Get-Content "servers.txt"
$Path = "\\D$\Temp"
$cred = "domain\user"
ForEach ($server in $servers) {
if (invoke-command -computername $server -credential $cred -ScriptBlock {Test-Path -Path "\\$server\$Path"})
}
PD: All this option works in a server of my domain without specify another credentials.
Besides the syntax issue with missing the script block after your if statement, this should work as long as you specify the variables as remote ones. Use $using or pass it as an argument with -ArgumentList.
$servers = Get-Content "servers.txt"
$Path = "\\c$\Temp"
$cred = "domain\user"
ForEach ($server in $servers) {
if (
invoke-command -computername $server -ScriptBlock {
Test-Path -Path "\\$using:server\$using:Path"
}
) { <#do code here#> }
}
If you run the shell with the proper credentials to begin with, all youd have to do is use Test-Path directly but I understand that you'd like to try using the -Credential parameter.

Powershell | second Invoke Using:Variable

i have a simple question. I want to use a variable in an invoke-command and pass this to the second invoke-command. In the second invoke-command the variable is empty
here is my code:,
$WaitSeconds = 1234
Invoke-Command -ComputerName $remote -Credential $cred -ScriptBlock {
$computer = dsquery computer "DC=domain,DC=local" -o rdn
$computers = $computer -replace ('"', '')
write-host $computers
foreach ($computer in $computers) {
if ($computer -notmatch "AZUREADSSOACC")
{
Invoke-Command -ComputerName $computer -Credential $using:cred -ScriptBlock {
#### here script
shutdown -s -t $using:waitseconds
}
}
}
}
In the second invoke-command the variable is empty
That's because the variable doesn't exist inside the calling session (the first Invoke-Command call's execution context).
Make sure you first instruct PowerShell to copy the variable to the "outer" Invoke-Command call:
$WaitSeconds = 1234
Invoke-Command {
# PowerShell will now copy the $WaitSeconds variable value from the calling scope to this remote session
$WaitSeconds = $using:WaitSeconds
# ...
Invoke-Command {
# This will now resolve the variable value correctly
shutdown -s -t $using:WaitSeconds
}
}
i got it
my solution is:
$WaitSeconds = 1234
Invoke-Command {
param($WaitSeconds)
$WaitSeconds = $WaitSeconds
# ...
Invoke-Command {
param($WaitSeconds)
shutdown -s -t $WaitSeconds
} -ArgumentList $WaitSeconds
} -ArgumentList $WaitSeconds

Passing Credentials In PowerShell multi line scriptblock

I have passed credentials before using a credential parameter in my Scriptblock and passing the value via an argument. I expect the size of my Scriptblock to grow so I am using a here string to keep it clean then I convert the string into a Scriptblock. How do I add a credential parameter and argument to my example below. I know the $credential value I use to get my remote session below has the necessary priveleges to get the file I want as I have tested it on the remote machine. So if possible I would like to pass this same credential.
$user = 'MyDomain\username'
$password = ConvertTo-SecureString 'mypassword' -asplaintext -force
$credential = New-Object -typename System.Management.Automation.PSCredential -ArgumentList $user, $password
try {
$s = New-PSSession -ComputerName MyRemoteComputer -Credential $credential
$remoteCommand = #"
New-PSDrive -Name 'P' -PSProvider 'FileSystem' -Root '\\main-server\Folders\DevOps\Projetcs\Juniper'
Get-Item -Path P:\V1.6\InstallFiles\Install.bat
"#
$scriptBlock = [Scriptblock]::Create($remoteCommand)
$status = Invoke-Command -Session $s -ScriptBlock $scriptBlock
Write-Host $status
Exit-PSSession -Session $s
}
catch {
#TODO Add exception handling
}

How can I continue lines of a script block in PowerShell?

I'm trying to write a Powershell function to create an OrganizationalUnit. I'm a PS newbie but I've pieced together:
function makeOU ($cn, $path)
{
$sb = [scriptblock]::Create(
"New-ADOrganizationalUnit $cn -path `"$path`"
-ProtectedFromAccidentalDeletion 0"
)
Invoke-Command -ComputerName $server -Credential $Credential `
-ScriptBlock $sb
}
But when I invoke this later in the script, I get a message that -ProtectedFromAccidentalDeletion is an unknown cmdlet. If I make the command one line
"New-ADOrganizationalUnit $cn -path `"$path`" -ProtectedFromAccidentalDeletion 0"
it works.
As I see it, at the end of
"New-ADOrganizationalUnit $cn -path `"$path`"
there is an open parenthesis and an open quote so PS should be looking for more input. Ending that line with a back tick didn't help. Nor did converting the argument to Create() to the form #" ... "#. (This is different from Why I am getting PowerShell Parsing Error? in that I don't have any back ticks, certainly none with spaces after them.)
If I'm making an newbie error here and there's a better way to pass the function parameters to Invoke-Command, I'm open to rewriting but failing that, how can break the string passed to Create() onto multiple lines?
Chris Nelson: Your "if you really want to" makes it sound like I'm doing something
grossly anti-idomatic to PS. I'm perfectly willing to believe that so
I wonder how you'd write it.
Thing is, backticks are frowned upon in Posh community:
READ-02 Avoid backticks
In general, the community feels you should avoid using those backticks
as "line continuation characters" when possible. They're hard to read,
easy to miss, and easy to mistype. Also, if you add an extra
whitespace after the backtick in the above example, then the command
won't work. The resulting error is hard to correlate to the actual
problem, making debugging the issue harder.
Maximum Line Length
The preferred way to avoid long lines is to use splatting (see
About_Splatting) and PowerShell's implied line continuation inside
parentheses, brackets, and braces -- these should always be used in
preference to the backtick for line continuation when applicable, even
for strings
Since you've asked, how I'd write it, here is some examples:
"Structured"
function makeOU ($cn, $path)
{
$Template = 'New-ADOrganizationalUnit {0} -Path "{1}" -ProtectedFromAccidentalDeletion 0'
$ScriptBlock = [scriptblock]::Create(($Template -f $cn, $path))
Invoke-Command -ComputerName $server -Credential $Credential -ScriptBlock $ScriptBlock
}
"Splatted"
function makeOU ($cn, $path)
{
$Template = 'New-ADOrganizationalUnit {0} -Path "{1}" -ProtectedFromAccidentalDeletion 0'
$ScriptBlock = $Template -f $cn, $path
$Splat = #{
ComputerName = $server
Credential = $Credential
ScriptBlock = [scriptblock]::Create($ScriptBlock)
}
Invoke-Command #Splat
}
"Oneliner"
function makeOU ($cn, $path)
{
Invoke-Command -ComputerName $server -Credential $Credential -ScriptBlock (
[scriptblock]::Create(
('New-ADOrganizationalUnit {0} -Path "{1}" -ProtectedFromAccidentalDeletion 0' -f $cn, $path)
)
)
}
# Or, using parentheses:
function makeOU ($cn, $path)
{
Invoke-Command -ComputerName (
$server
) -Credential (
$Credential
) -ScriptBlock (
[scriptblock]::Create(
('New-ADOrganizationalUnit {0} -Path "{1}" -ProtectedFromAccidentalDeletion 0' -f $cn, $path)
)
)
}
Well, if you really want to format it that way, try this:
function makeOU ($cn, $path)
{
$sb = [scriptblock]::Create(
"New-ADOrganizationalUnit $cn -path (
`"$path`"
) -ProtectedFromAccidentalDeletion 0"
)
Invoke-Command -ComputerName $server -Credential $Credential `
-ScriptBlock $sb
}
If you know how-to escape inner " double quotes, and you know how-to spread a command on multiple lines using a Grave Accent as an escape character:
Invoke-Command -ComputerName $server -Credential $Credential `
-ScriptBlock $sb
then you know how-to do the last in a script block as well: just double the Grave Accent character:
$sb = [scriptblock]::Create(
"New-ADOrganizationalUnit $cn -path `"$path`" ``
-ProtectedFromAccidentalDeletion 0"
)

Remote Registry using Enter-PSSession

I am trying to read strings in a remote registry. When I run the script I am working on, it connects to the workstation in the list, but it only reads the local computer when running, not the remote. any Ideas?
#create open dialog box
Function Get-FileName($initialDirectory)
{
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' );
$d = New-Object Windows.Forms.OpenFileDialog;
$d.ShowHelp = $True;
$d.filter = "Comma Separated Value (*.csv)| *.csv";
$d.ShowDialog( ) | Out-Null;
$d.filename;
}
# Set Variables with arguments
$strFile = Get-FileName;
$strComputer = Get-Content $strFile;
$date = Get-Date -Format "MM-dd-yyyy";
$outputFile = "C:\PowerShell\Reports";
$cred = Get-Credential
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
..
..
}
The above code won't work. Enter-PSSession is not for using in a script. Anything written after that in a script won't run.
Instead, use Invoke-Command and pass rest of the script block as a parameter value. For example,
foreach ($computer in $strComputer) {
Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
}
As the comments already explained, Enter-PSSession is for interactive use. To read remote registry entries, there are several ways.
Use plain reg.exe, it works well enough. Like so,
foreach($computer in $strComputers) {
reg query \\$computer\hklm\software\Microsoft\Windows\CurrentVersion\Reliability /v LastComputerName
}
Use PSSessions. Create a session and Invoke-Command to read registry. Like so,
function GetRegistryValues {
param($rpath, $ivalue)
Set-Location $rpath
$systemInfo = (Get-ItemProperty .).$ivalue
Write-Host $systemInfo
}
$session = New-PSSession -ComputerName $computer
Invoke-Command -Session $session -Scriptblock ${function:GetRegistryValues} `
-argumentlist "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability",`
"LastComputerName"
Remove-PSSession $session
Use .Net classes, Microsoft.Win32.RegistryKey. Like so,
$sk = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $server)
$k = $sk.opensubkey("SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability", $false)
write-host $k.getvalue("LastComputerName")