Copy files from one folder to another remotely in PowerShell - powershell

I want to copy files from one folder on F: to H: on a remote machine. I write the following script but not working, tried with list down all the files, but am getting following error:
Create-Credentials : The term 'Create-Credentials' is not recognized as the name of a cmdlet, function, script file, or operable program. Check
For example:
My remote server is 143.56.23.99
User name : jyoti
Password: Test123#
Source File: F:\SourceFolder\
Destination : H:\Destination\
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)] [string] $Computer,
[Parameter(Mandatory=$True)] [string] $Path,
[Parameter(Mandatory=$True)] [string] $Destination,
[Parameter(Mandatory=$True)] [string] $Username,
[Parameter(Mandatory=$True)] [string] $Password,
[Parameter(Mandatory=$False)] [PSCredential] $Credential
)
if($UserName -and $Password) {
$Credential = Create-Credentials -Username $Username -Password $Password
} elseif(-not ($Credential)) {
throw("Unable to authenticate. A username and password or pscredentials must be provided.")
}
$Items = (Get-ChildItem $Path).FullName
$NetworkLocation = Join-Path -Path "\\$Computer" -ChildPath ($Destination.Replace(':', '$'))
foreach ($Item in $Items) {
Write-Host "------------>$Item"
}

I'd recommend reading up on using Credentials as it will help you understand what's going on with your code.
Create-Credentials is not a native powershell cmdlet, either go back to where you got your code from and get that function too.
Or remove the function and use native powershell code.
Replace:
$Credential = Create-Credentials -Username $Username -Password $Password
with:
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username,($Password | ConvertTo-SecureString -AsPlainText -Force)

Related

Powershell Invoke-Expression A parameter cannot be found that matches parameter name = ""

Im trying to invoke a Ps script from antoher script. The scripts are both in the same path.
Also the script I'm trying to invoke takes 4 parameters.
Whem i execute that file from powershell with the parameters, then it works without errors.
But invoking it with the Invoke-Expression Command does not work.
Keep getting the error :
'A parameter cannot be found that matches parameter name'
Script with the Paramters :
param ([Parameter(Mandatory = $true)]
[string] $Samname,
[string] $Fullname,
[string] $Password,
[string] $Groups
)
$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force
New-localuser -name $Samname -FullName $Fullname -password $securePassword -PasswordNeverExpires -UserMayNotChangePassword
#Add the User to the Groups
$localGroups = Get-LocalGroup
[string[]]$GroupArray = $Groups.Split(' ')
foreach ($localgroup in $localGroups){
foreach ($group in $GroupArray){
$group = $group.Replace(';', '')
if ($group.toString().Equals($localgroup.toString())){
Add-LocalGroupMember -Group $localgroup -Member $samname
}
}
}
Script with Invoke-Expression command :
$XmlDocument = 'C:\SomeFile\toPs\TmpUser.config'
[XML]$XmlFile = Get-Content $XmlDocument
[string] $Samname = $XmlFile.User.Username
[string] $Fullname = $XmlFile.User.Fullname
[string] $Password = $XmlFile.User.Password
[string] $Groups = $XmlFile.User.Groups
$script = ".\CreateUser.ps1"
Invoke-Expression $script "-Samname $Samname -Fullname $Fullname -Password $Password -Groups $Groups"
I'm not that sure if I'm using the params the right way, when I invoke the script.
Thanks for your help :)
It's hard to tell exactly what's tripping up Invoke-Expression without the full extent of the error message, but the good news is that you don't need Invoke-Expression at all!
Use the invocation operator (also known as the "call operator", &) instead, it natively supports parameter binding:
$XmlDocument = 'C:\SomeFile\toPs\TmpUser.config'
[XML]$XmlFile = Get-Content $XmlDocument
[string] $Samname = $XmlFile.User.Username
[string] $Fullname = $XmlFile.User.Fullname
[string] $Password = $XmlFile.User.Password
[string] $Groups = $XmlFile.User.Groups
$script = ".\CreateUser.ps1"
& $script -Samname $Samname -Fullname $Fullname -Password $Password -Groups $Groups

Execute a remote generic Powershell script with generic parameters

I need to write a Powershell script (let's call it "the controller script") that is able to call a generic remote Powershell script passing generic parameters.
The controller script accepts as parameters the hostname, the credentials, the remote script path and the remote script's parameters as a hashtable.
The remote script, instead, may be any script which accepts any string parameter.
Using the hashtable parameter for the controller script is useful in that I can pass a dynamic dictionary of parameters (that depends on the controller call) while making PS do the work of "transform" the dictionary to a list of string parameters like -Param1 Value1 -Param2 Value2.
I got some ideas from this answer and this is what I did (the "controller" script):
Param(
[string] $ComputerName,
[string] $Username,
[string] $Password,
[string] $ScriptPath,
[string] $Parameters
)
$EncPassword = ConvertTo-SecureString $Password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($Username,$EncPassword)
$ScriptBlock = [Scriptblock]::Create(".$ScriptPath $(&{$args} #Parameters)")
Invoke-Command -ComputerName $ComputerName -Credential $cred -Scriptblock $ScriptBlock
Then I execute it via the PS prompt this way:
.\controller.ps1 -ComputerName MACHINE_NAME -Username USERNAME -Password PASSWORD -ScriptPath "D:\TestScript.ps1" -Parameters #{AParameter = "asd"}
The execution fails with this error:
The term '.D:\TestScript.ps1' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is
correct and try again.
So it seems that the Scriptblock refers to a local script (on the controller's machine), not to the remote machine where the target script resides.
Is there any way to let me execute a remote PS script using the hashtable parameter, which is the desired flexibility requirement?
UPDATE 1
I added a whitespace between the dot and the $ScriptPath variable in the ScriptBlock definition but the error is the same (without the dot).
$ScriptBlock = [Scriptblock]::Create(". $ScriptPath $(&{$args} #Parameters)")
The term 'D:\TestScript.ps1' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is
correct and try again.
UPDATE 2
I've found a way to call the remote script without the parameters.
Param(
[string] $ComputerName,
[string] $Username,
[string] $Password,
[string] $ScriptPath,
[hashtable] $Parameters
)
$EncPassword = ConvertTo-SecureString $Password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($Username,$EncPassword )
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {Invoke-Expression $args[0]} -ArgumentList $ScriptPath
I get the remote script output without parameters. Now the left thing to do is splatting the hashtable $Parameters remotely when calling the script at the remote path $ScriptPath. Do you have any idea? I made some trials but nothing worked.
I finally found the solution
controller.ps1
Param(
[string] $ComputerName,
[string] $Username,
[string] $Password,
[string] $ScriptPath,
[hashtable] $Parameters
)
$EncPassword = ConvertTo-SecureString $Password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($Username,$EncPassword )
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {
$params = $Using:Parameters
Invoke-Expression "$Using:ScriptPath #params"
}
As you can see here we use the $Using variable in the ScriptBlock to retrieve the outside variables ($ScriptPath and $Parameters) and then we call the remote script splatting the parameters hashtable.
I'd recommend using the FilePath parameter in Invoke-Command rather than the scriptblock. That way PSRemoting does all the heavy lifting.
Invoke-Command -ComputerName $ComputerName -Credential $cred -FilePath $ScriptPath -ArgumentList $Parameters,$args
Otherwise you can use Sessions to copy the file and run the file.
$Session = New-PSSession -ComputerName $Computer -Credential $credential
Copy-Item -Path $ScriptPath -Destination $Dest -ToSession $Session
Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
Edit: This may be more of what you were looking for:
The first issue seems to be that you don't have the correct path of the script or your user account doesn't have access to that path. Thats the error you are seeing. I've tested on my systems a few different ways and dot-sourcing should work.
The previous method expands the variables too early to use splatting. This is how to get splatting to work:
Invoke-command -ScriptBlock {$a = $args[0]; & D:\full\path\to\testscript.ps1 #a $args[1]} -ArgumentList $Parameters,$additionalArgs
Be sure to get a hash table instead of string in the params
[HashTable] $Parameters

Powershell not recognizing boolean argument

I have the following PS script
param (
# FQDN or IP address of the Domain Controller
[Parameter(Mandatory=$True)]
[string]$ADaddress,
# Active directory domain name
# example: directory.local
[Parameter(Mandatory=$True)]
[string]$ADDomainName,
# Domain admin
# example: administrator#directory.local
[Parameter(Mandatory=$True)]
[string]$domainAdmin,
# Domain admin password
[Parameter(Mandatory=$True)]
[string]$domainAdminPassword,
# User to be added
# example: testUser
[Parameter (Mandatory=$True)]
[string]$newUsername,
# Password of th user to be added
# example: 1!2#4%6
[Parameter (Mandatory=$True)]
[string]$newPassword,
# SAM account name of the user to added
# example: testuser
[Parameter (Mandatory=$True)]
[string]$newSamAccountName,
# Display name of the user to added
# example: "Test user for test purposes"
[Parameter (Mandatory=$True)]
[string]$newUserDisplayName
)
$domainAdminSecurePassword = $domainAdminPassword | ConvertTo-SecureString -asPlainText -Force
$domainAdminCredential = New-Object System.Management.Automation.PSCredential($domainAdmin, $domainAdminSecurePassword)
$newUserSecurePassword = $newPassword | ConvertTo-SecureString -asPlainText -Force
$UPN= $newUsername+"#"+$ADDomainName
Invoke-Command -ComputerName $ADaddress -Credential $domainAdminCredential `
-ScriptBlock {`
param($newUsername, $newUserSecurePassword, $newSamAccountName, $newUserDisplayName, $UPN) `
new-aduser -name $newUsername -AccountPassword $newUserSecurePassword -Enabled $true -SamAccountName $newSamAccountName -DisplayName $newUserDisplayName -UserPrincipalName $UPN -PasswordNeverExpires $true`
} `
-ArgumentList $newUsername, $newUserSecurePassword, $newSamAccountName, $newUserDisplayName, $UPN
Tho problem I get when invoking this script is:
Cannot convert 'System.String' to the type 'System.Nullable`1[System.Boolean]' required by parameter 'PasswordNeverExpires'.
I tried passing 1 instead, passing [bool]$true but the result remains the same. I am new to PS and I'm lost here. Can anyone shine some light on what the problem may be?
Alright, I found what the problem was.
Changed:
-PasswordNeverExpires $true`
to
-PasswordNeverExpires $true `
(added a space after true)
replacing $true with a variable did it for me.
So this:
$command = 'New-CMApplicationDeployment -Name $Name -CollectionName $Col -OverrideServiceWindow $true -Comment $Com -AvailableDateTime $Adt -DeployAction Install -DeployPurpose Available -UserNotification DisplaySoftwareCenterOnly'
Invoke-Expression -Command "& $command"
became:
$t = $true
$command = 'New-CMApplicationDeployment -Name $Name -CollectionName $Col -OverrideServiceWindow $t -Comment $Com -AvailableDateTime $Adt -DeployAction Install -DeployPurpose Available -UserNotification DisplaySoftwareCenterOnly'
Invoke-Expression -Command "& $command"
Its dumb but it worked.

Power Shell - Copy-Item UNC Path, Cannot Find Path... Does not Exist [duplicate]

I need to install an application in several remote servers in quiet mode. I have created a script (Installer.ps1) like below using Powershell v3.0:
param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
$ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe"; & $ExePath /q;}
Invoke-Command -ComputerName (Get-Content Servers.txt) -Credential $mycreds $ScrBlock -ArgumentList $InstallerFolderPath
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password
Then I call the script like below (Installer folder path can have white spaces and the executable ServerReleaseManager.exe accepts argument):
.\Installer.ps1 -ServerNameFilePath Servers.txt -InstallerFolderPath "\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7" -UserName "Domain\User" -Password "Test123"
I am getting below CommandNotFoundException always:
The term '\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7\ServerReleaseManager.exe' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
I have tried other options like using -FilePath with Invoke-Command but same error. I am really blocked here. Can you please let me know why this error has shown? How to resolve the error? Or are there any better ways to deal with this. Thanks for your help.
This sounds like a double-hop authentication issue. Once you're remoted into the server, you can't access a file share on a third server because you can't pass your kerberos-based authentication to it.
You could try copying from the share to the remote server, first (this has to be done on the computer executing the script), and then in the scriptblock refer to the (now local) path.
You could set up CredSSP which isn't a great idea for this purpose.
Basically, you need to avoid connecting to one machine, then connecting to another through that connection.
Code that implements the workaround I'm describing:
param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
$ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe"; & $ExePath /q;}
Get-Content Servers.txt | ForEach-Item {
$remoteDest = "\\$_\c`$\some\temp\folder"
$localDest = "C:\some\temp\folder" | Join-Path -ChildPath ($InstallerFolderPath | Split-Path -Leaf)
try {
Copy-Item -Path $InstallerFolderPath -Destination $dest -Force
Invoke-Command -ComputerName $_ -Credential $mycreds $ScrBlock -ArgumentList $localDest
finally {
Remove-Item $remoteDest -Force -ErrorAction Ignore
}
}
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password
Notes
This is untested.
As mentioned by Swonkie, you should set your parameters as mandatory if that's what you're looking to achieve (not addressed in my code).
You shouldn't pass separate plain text user name and password parameters and then convert them to a credential object. Instead pass a single [PSCredential] parameter. You can use a default value that prompts, like [PSCredential] $Cred = (Get-Credential). (this is not addressed in my code either).
Desired state configuration can be used to install software on target machines. I assume this can work around the double hop issue.
http://technet.microsoft.com/de-de/library/dn282132.aspx
http://technet.microsoft.com/de-de/library/dn282129.aspx
By the way - dont throw errors for missing mandatory arguments. Let PowerShell handle that - it's much more user friendly:
param(
[parameter(Mandatory=$true)] [string] $ServerNameFilePath,
[parameter(Mandatory=$true)] [string] $InstallerFolderPath,
[parameter(Mandatory=$true)] [string] $UserName,
[parameter(Mandatory=$true)] [string] $Password
)
Here I created a new PSsession to each server in the list and used the invoke command to target that server's session. I've tested it in my environment and it successfully installs my exe application with a /q switch on my remote servers.
This method however does not tell if you the command ran successfully on the remote side, you would have to logon to the server or do a test-path to the expected location of the installed files for validation. Also, PSsessions are held open until the console that launched the command is closed. If a PSsession ends before the install completes, the install will fail.
Function InstallApp {
param(
[parameter(Mandatory=$true)] [String] $ServerNameFilePath,
[parameter(Mandatory=$true)] [String] $InstallerFilePath,
[parameter(Mandatory=$true)] [String] $CommandArgument,
[parameter(Mandatory=$true)] [String] $UserName,
[parameter(Mandatory=$true)] [String] $Password
)
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
Get-Content $ServerNameFilePath | ForEach-Object {
$remoteSession = new-PSSession $_ -Credential $mycreds
Invoke-command -Session $remoteSession -Scriptblock {& ($args[0]) #($args[1])} -ArgumentList $InstallerFilePath,$CommandArgument
}
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFilePath $InstallerFilePath -CommandArgument $CommandArgument -UserName $UserName -Password $Password

Issues with Invoke-Command while installing softwares in remote server

I need to install an application in several remote servers in quiet mode. I have created a script (Installer.ps1) like below using Powershell v3.0:
param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
$ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe"; & $ExePath /q;}
Invoke-Command -ComputerName (Get-Content Servers.txt) -Credential $mycreds $ScrBlock -ArgumentList $InstallerFolderPath
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password
Then I call the script like below (Installer folder path can have white spaces and the executable ServerReleaseManager.exe accepts argument):
.\Installer.ps1 -ServerNameFilePath Servers.txt -InstallerFolderPath "\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7" -UserName "Domain\User" -Password "Test123"
I am getting below CommandNotFoundException always:
The term '\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7\ServerReleaseManager.exe' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
I have tried other options like using -FilePath with Invoke-Command but same error. I am really blocked here. Can you please let me know why this error has shown? How to resolve the error? Or are there any better ways to deal with this. Thanks for your help.
This sounds like a double-hop authentication issue. Once you're remoted into the server, you can't access a file share on a third server because you can't pass your kerberos-based authentication to it.
You could try copying from the share to the remote server, first (this has to be done on the computer executing the script), and then in the scriptblock refer to the (now local) path.
You could set up CredSSP which isn't a great idea for this purpose.
Basically, you need to avoid connecting to one machine, then connecting to another through that connection.
Code that implements the workaround I'm describing:
param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
$ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe"; & $ExePath /q;}
Get-Content Servers.txt | ForEach-Item {
$remoteDest = "\\$_\c`$\some\temp\folder"
$localDest = "C:\some\temp\folder" | Join-Path -ChildPath ($InstallerFolderPath | Split-Path -Leaf)
try {
Copy-Item -Path $InstallerFolderPath -Destination $dest -Force
Invoke-Command -ComputerName $_ -Credential $mycreds $ScrBlock -ArgumentList $localDest
finally {
Remove-Item $remoteDest -Force -ErrorAction Ignore
}
}
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password
Notes
This is untested.
As mentioned by Swonkie, you should set your parameters as mandatory if that's what you're looking to achieve (not addressed in my code).
You shouldn't pass separate plain text user name and password parameters and then convert them to a credential object. Instead pass a single [PSCredential] parameter. You can use a default value that prompts, like [PSCredential] $Cred = (Get-Credential). (this is not addressed in my code either).
Desired state configuration can be used to install software on target machines. I assume this can work around the double hop issue.
http://technet.microsoft.com/de-de/library/dn282132.aspx
http://technet.microsoft.com/de-de/library/dn282129.aspx
By the way - dont throw errors for missing mandatory arguments. Let PowerShell handle that - it's much more user friendly:
param(
[parameter(Mandatory=$true)] [string] $ServerNameFilePath,
[parameter(Mandatory=$true)] [string] $InstallerFolderPath,
[parameter(Mandatory=$true)] [string] $UserName,
[parameter(Mandatory=$true)] [string] $Password
)
Here I created a new PSsession to each server in the list and used the invoke command to target that server's session. I've tested it in my environment and it successfully installs my exe application with a /q switch on my remote servers.
This method however does not tell if you the command ran successfully on the remote side, you would have to logon to the server or do a test-path to the expected location of the installed files for validation. Also, PSsessions are held open until the console that launched the command is closed. If a PSsession ends before the install completes, the install will fail.
Function InstallApp {
param(
[parameter(Mandatory=$true)] [String] $ServerNameFilePath,
[parameter(Mandatory=$true)] [String] $InstallerFilePath,
[parameter(Mandatory=$true)] [String] $CommandArgument,
[parameter(Mandatory=$true)] [String] $UserName,
[parameter(Mandatory=$true)] [String] $Password
)
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)
Get-Content $ServerNameFilePath | ForEach-Object {
$remoteSession = new-PSSession $_ -Credential $mycreds
Invoke-command -Session $remoteSession -Scriptblock {& ($args[0]) #($args[1])} -ArgumentList $InstallerFilePath,$CommandArgument
}
}
InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFilePath $InstallerFilePath -CommandArgument $CommandArgument -UserName $UserName -Password $Password