Issues with Invoke-Command while installing softwares in remote server - powershell

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

Related

Credential Without Asking For Prompts In Powershell [duplicate]

I'd like to restart a remote computer that belongs to a domain. I have an administrator account but I don't know how to use it from powershell.
I know that there is a Restart-Computer cmdlet and that I can pass credential but if my domain is for instance mydomain, my username is myuser and my password is mypassword what's the right syntax to use it?
I need to schedule the reboot so I don't have to type the password.
The problem with Get-Credential is that it will always prompt for a password. There is a way around this however but it involves storing the password as a secure string on the filesystem.
The following article explains how this works:
Using PSCredentials without a prompt
In summary, you create a file to store your password (as an encrypted string). The following line will prompt for a password then store it in c:\mysecurestring.txt as an encrypted string. You only need to do this once:
read-host -assecurestring | convertfrom-securestring | out-file C:\mysecurestring.txt
Wherever you see a -Credential argument on a PowerShell command then it means you can pass a PSCredential. So in your case:
$username = "domain01\admin01"
$password = Get-Content 'C:\mysecurestring.txt' | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential `
-argumentlist $username, $password
$serverNameOrIp = "192.168.1.1"
Restart-Computer -ComputerName $serverNameOrIp `
-Authentication default `
-Credential $cred
<any other parameters relevant to you>
You may need a different -Authentication switch value because I don't know your environment.
There is another way, but...
DO NOT DO THIS IF YOU DO NOT WANT YOUR PASSWORD IN THE SCRIPT FILE
(It isn't a good idea to store passwords in scripts, but some of us just like to know how.)
Ok, that was the warning, here's the code:
$username = "John Doe"
$password = "ABCDEF"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$cred will have the credentials from John Doe with the password "ABCDEF".
Alternative means to get the password ready for use:
$password = convertto-securestring -String "notverysecretpassword" -AsPlainText -Force
Regarding storing credentials, I use two functions(that are normally in a module that is loaded from my profile):
#=====================================================================
# Get-MyCredential
#=====================================================================
function Get-MyCredential
{
param(
$CredPath,
[switch]$Help
)
$HelpText = #"
Get-MyCredential
Usage:
Get-MyCredential -CredPath `$CredPath
If a credential is stored in $CredPath, it will be used.
If no credential is found, Export-Credential will start and offer to
Store a credential at the location specified.
"#
if($Help -or (!($CredPath))){write-host $Helptext; Break}
if (!(Test-Path -Path $CredPath -PathType Leaf)) {
Export-Credential (Get-Credential) $CredPath
}
$cred = Import-Clixml $CredPath
$cred.Password = $cred.Password | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PsCredential($cred.UserName, $cred.Password)
Return $Credential
}
And this one:
#=====================================================================
# Export-Credential
# Usage: Export-Credential $CredentialObject $FileToSaveTo
#=====================================================================
function Export-Credential($cred, $path) {
$cred = $cred | Select-Object *
$cred.password = $cred.Password | ConvertFrom-SecureString
$cred | Export-Clixml $path
}
You use it like this:
$Credentials = Get-MyCredential (join-path ($PsScriptRoot) Syncred.xml)
If the credential file doesnt exist, you will be prompted the first time, at that point it will store the credentials in an encrypted string inside an XML file. The second time you run that line, the xmlfile is there and will be opened automatically.
I have to run SCOM 2012 functions from a remote server that requires a different credential. I avoid clear-text passwords by passing the output of a password decryption function as input to ConvertTo-SecureString. For clarity, this is not shown here.
I like to strongly type my declarations. The type declaration for $strPass works correctly.
[object] $objCred = $null
[string] $strUser = 'domain\userID'
[System.Security.SecureString] $strPass = ''
$strPass = ConvertTo-SecureString -String "password" -AsPlainText -Force
$objCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($strUser, $strPass)
Here are two ways you could do this, if you are scheduling the reboot.
First you could create a task on one machine using credentials that have rights needed to connect and reboot another machine. This makes the scheduler responsible for securely storing the credentials. The reboot command (I'm a Powershell guy, but this is cleaner.) is:
SHUTDOWN /r /f /m \\ComputerName
The command line to create a scheduled task on the local machine, to remotely reboot another, would be:
SCHTASKS /Create /TN "Reboot Server" /TR "shutdown.exe /r /f /m \\ComputerName" /SC ONCE /ST 00:00 /SD "12/24/2012" /RU "domain\username" /RP "password"
I prefer the second way, where you use your current credentials to create a scheduled task that runs with the system account on a remote machine.
SCHTASKS /Create /TN "Reboot Server" /TR "shutdown.exe /r /f" /SC ONCE /ST 00:00 /SD "12/24/2012" /RU SYSTEM /S ComputerName
This also works through the GUI, just enter SYSTEM as the user name, leaving the password fields blank.
I saw one example that uses Import/Export-CLIXML.
These are my favorite commands for the issue you're trying to resolve. And the simplest way to use them is.
$passwordPath = './password.txt'
if (-not (test-path $passwordPath)) {
$cred = Get-Credential -Username domain\username -message 'Please login.'
Export-CliXML -InputObject $cred -Path $passwordPath
}
$cred = Import-CliXML -path $passwordPath
So if the file doesn't locally exist it will prompt for the credentials and store them. This will take a [pscredential] object without issue and will hide the credentials as a secure string.
Finally just use the credential like you normally do.
Restart-Computer -ComputerName ... -Credentail $cred
Note on Securty:
Securely store credentials on disk
When reading the Solution, you might at first be wary of storing a password on disk.
While it is natural (and prudent) to be cautious of littering your hard drive with
sensitive information, the Export-CliXml cmdlet encrypts credential objects using the
Windows standard Data Protection API. This ensures that only your user account can
properly decrypt its contents. Similarly, the ConvertFrom-SecureString cmdlet also
encrypts the password you provide.
Edit: Just reread the original question. The above will work so long as you've initialized the [pscredential] to the hard disk. That is if you drop that in your script and run the script once it will create that file and then running the script unattended will be simple.
read-host -assecurestring | convertfrom-securestring | out-file C:\securestring.txt
$pass = cat C:\securestring.txt | convertto-securestring
$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist "test",$pass
$mycred.GetNetworkCredential().Password
Be very careful with storing passwords this way... it's not as secure as ...
Solution
$userName = 'test-domain\test-login'
$password = 'test-password'
$pwdSecureString = ConvertTo-SecureString -Force -AsPlainText $password
$credential = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList $userName, $pwdSecureString
For Build Machines
In the previous code replace user name and password values by secret ("hidden from logs") environment variables of your build-machine
Test results by
'# Results'
$credential.GetNetworkCredential().Domain
$credential.GetNetworkCredential().UserName
$credential.GetNetworkCredential().Password
and you'll see
# Results
test-domain
test-login
test-password
This is what I use and works for me.
$User="Domain\Username"
$Password=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('VABlAHMAdABQAGEAcwBzAHcAbwByAGQA'))
$SecurePassword = New-Object -TypeName System.Security.SecureString
$Password.ToCharArray() | ForEach-Object {$SecurePassword.AppendChar($_)}
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
And to get VABlAHMAdABQAGEAcwBzAHcAbwByAGQA I do this:
To Encode $EString means Encrypted String and $DString means Decrypted String
$EString = Read-Host "Type Text to Encode" -AsSecureString
$BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($EString)
$DString=[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$Encoded=[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($DString))
$Encoded # VABlAHMAdABQAGEAcwBzAHcAbwByAGQA
$DString # TestPassword
That way I can put any password I want on the script without too much hassle.
In case below code might help someone.
function Get-RemoteConnection()
{
//$serverIp can be passed as a parameter of the function.
//I just make it static for testing.
$serverIp = '192.168.100.137'
Enable-PSRemoting -Force
Set-Item wsman:\localhost\client\trustedhosts $serverIp
Restart-Service WinRM
#Set credentials needed for remote installation
$userName = "administrator"
$password = ConvertTo-SecureString "2020core0515" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList ($userName, $password)
$session = New-PSSession -ComputerName $serverIp -Credential $cred
$a = Invoke-Command -Session $session -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" | Select-Object DeviceID, #{label='UsedPercent'; expression={[Math]::Round((($_.size - $_.freespace) / $_.size) * 100, 2)}} }
Write-Host $a
return $session
}
function Delete-RemoteConnection($session)
{
Disconnect-PSSession $session | Out-Null
Disable-WSManCredSSP -Role Client
}
Instead of storing the encrypted password in a text file you can store it in the credential vault.
In the follwing example the user is prompted for a password only the first time, after that the password is retrieved from the credential vault.
# Load the Windows Runtime Class
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
$Vault = New-Object Windows.Security.Credentials.PasswordVault
$RESOURCE = "myresource"
$USERNAME = "myuser"
try {
$credentials = $Vault.Retrieve($RESOURCE,$USERNAME)
$pwd = $credentials.Password | ConvertTo-SecureString -Key (1..16)
}
catch {
$pwd = Read-Host "please enter your password:" -AsSecureString
$Encrypted = ConvertFrom-SecureString -SecureString $pwd -Key (1..16)
$credentials = new-object -Type Windows.Security.Credentials.PasswordCredential -ArgumentList $RESOURCE,$USERNAME,$Encrypted
$Vault.Add($credentials)
}
$cred = New-Object System.Management.Automation.PsCredential($USERNAME,$pwd)
why dont you try something very simple?
use psexec with command 'shutdown /r /f /t 0' and a PC list from CMD.

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

Copy files from one folder to another remotely in 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)

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

Using PowerShell credentials without being prompted for a password

I'd like to restart a remote computer that belongs to a domain. I have an administrator account but I don't know how to use it from powershell.
I know that there is a Restart-Computer cmdlet and that I can pass credential but if my domain is for instance mydomain, my username is myuser and my password is mypassword what's the right syntax to use it?
I need to schedule the reboot so I don't have to type the password.
The problem with Get-Credential is that it will always prompt for a password. There is a way around this however but it involves storing the password as a secure string on the filesystem.
The following article explains how this works:
Using PSCredentials without a prompt
In summary, you create a file to store your password (as an encrypted string). The following line will prompt for a password then store it in c:\mysecurestring.txt as an encrypted string. You only need to do this once:
read-host -assecurestring | convertfrom-securestring | out-file C:\mysecurestring.txt
Wherever you see a -Credential argument on a PowerShell command then it means you can pass a PSCredential. So in your case:
$username = "domain01\admin01"
$password = Get-Content 'C:\mysecurestring.txt' | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential `
-argumentlist $username, $password
$serverNameOrIp = "192.168.1.1"
Restart-Computer -ComputerName $serverNameOrIp `
-Authentication default `
-Credential $cred
<any other parameters relevant to you>
You may need a different -Authentication switch value because I don't know your environment.
There is another way, but...
DO NOT DO THIS IF YOU DO NOT WANT YOUR PASSWORD IN THE SCRIPT FILE
(It isn't a good idea to store passwords in scripts, but some of us just like to know how.)
Ok, that was the warning, here's the code:
$username = "John Doe"
$password = "ABCDEF"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$cred will have the credentials from John Doe with the password "ABCDEF".
Alternative means to get the password ready for use:
$password = convertto-securestring -String "notverysecretpassword" -AsPlainText -Force
Regarding storing credentials, I use two functions(that are normally in a module that is loaded from my profile):
#=====================================================================
# Get-MyCredential
#=====================================================================
function Get-MyCredential
{
param(
$CredPath,
[switch]$Help
)
$HelpText = #"
Get-MyCredential
Usage:
Get-MyCredential -CredPath `$CredPath
If a credential is stored in $CredPath, it will be used.
If no credential is found, Export-Credential will start and offer to
Store a credential at the location specified.
"#
if($Help -or (!($CredPath))){write-host $Helptext; Break}
if (!(Test-Path -Path $CredPath -PathType Leaf)) {
Export-Credential (Get-Credential) $CredPath
}
$cred = Import-Clixml $CredPath
$cred.Password = $cred.Password | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PsCredential($cred.UserName, $cred.Password)
Return $Credential
}
And this one:
#=====================================================================
# Export-Credential
# Usage: Export-Credential $CredentialObject $FileToSaveTo
#=====================================================================
function Export-Credential($cred, $path) {
$cred = $cred | Select-Object *
$cred.password = $cred.Password | ConvertFrom-SecureString
$cred | Export-Clixml $path
}
You use it like this:
$Credentials = Get-MyCredential (join-path ($PsScriptRoot) Syncred.xml)
If the credential file doesnt exist, you will be prompted the first time, at that point it will store the credentials in an encrypted string inside an XML file. The second time you run that line, the xmlfile is there and will be opened automatically.
I have to run SCOM 2012 functions from a remote server that requires a different credential. I avoid clear-text passwords by passing the output of a password decryption function as input to ConvertTo-SecureString. For clarity, this is not shown here.
I like to strongly type my declarations. The type declaration for $strPass works correctly.
[object] $objCred = $null
[string] $strUser = 'domain\userID'
[System.Security.SecureString] $strPass = ''
$strPass = ConvertTo-SecureString -String "password" -AsPlainText -Force
$objCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($strUser, $strPass)
Here are two ways you could do this, if you are scheduling the reboot.
First you could create a task on one machine using credentials that have rights needed to connect and reboot another machine. This makes the scheduler responsible for securely storing the credentials. The reboot command (I'm a Powershell guy, but this is cleaner.) is:
SHUTDOWN /r /f /m \\ComputerName
The command line to create a scheduled task on the local machine, to remotely reboot another, would be:
SCHTASKS /Create /TN "Reboot Server" /TR "shutdown.exe /r /f /m \\ComputerName" /SC ONCE /ST 00:00 /SD "12/24/2012" /RU "domain\username" /RP "password"
I prefer the second way, where you use your current credentials to create a scheduled task that runs with the system account on a remote machine.
SCHTASKS /Create /TN "Reboot Server" /TR "shutdown.exe /r /f" /SC ONCE /ST 00:00 /SD "12/24/2012" /RU SYSTEM /S ComputerName
This also works through the GUI, just enter SYSTEM as the user name, leaving the password fields blank.
I saw one example that uses Import/Export-CLIXML.
These are my favorite commands for the issue you're trying to resolve. And the simplest way to use them is.
$passwordPath = './password.txt'
if (-not (test-path $passwordPath)) {
$cred = Get-Credential -Username domain\username -message 'Please login.'
Export-CliXML -InputObject $cred -Path $passwordPath
}
$cred = Import-CliXML -path $passwordPath
So if the file doesn't locally exist it will prompt for the credentials and store them. This will take a [pscredential] object without issue and will hide the credentials as a secure string.
Finally just use the credential like you normally do.
Restart-Computer -ComputerName ... -Credentail $cred
Note on Securty:
Securely store credentials on disk
When reading the Solution, you might at first be wary of storing a password on disk.
While it is natural (and prudent) to be cautious of littering your hard drive with
sensitive information, the Export-CliXml cmdlet encrypts credential objects using the
Windows standard Data Protection API. This ensures that only your user account can
properly decrypt its contents. Similarly, the ConvertFrom-SecureString cmdlet also
encrypts the password you provide.
Edit: Just reread the original question. The above will work so long as you've initialized the [pscredential] to the hard disk. That is if you drop that in your script and run the script once it will create that file and then running the script unattended will be simple.
read-host -assecurestring | convertfrom-securestring | out-file C:\securestring.txt
$pass = cat C:\securestring.txt | convertto-securestring
$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist "test",$pass
$mycred.GetNetworkCredential().Password
Be very careful with storing passwords this way... it's not as secure as ...
Solution
$userName = 'test-domain\test-login'
$password = 'test-password'
$pwdSecureString = ConvertTo-SecureString -Force -AsPlainText $password
$credential = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList $userName, $pwdSecureString
For Build Machines
In the previous code replace user name and password values by secret ("hidden from logs") environment variables of your build-machine
Test results by
'# Results'
$credential.GetNetworkCredential().Domain
$credential.GetNetworkCredential().UserName
$credential.GetNetworkCredential().Password
and you'll see
# Results
test-domain
test-login
test-password
This is what I use and works for me.
$User="Domain\Username"
$Password=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('VABlAHMAdABQAGEAcwBzAHcAbwByAGQA'))
$SecurePassword = New-Object -TypeName System.Security.SecureString
$Password.ToCharArray() | ForEach-Object {$SecurePassword.AppendChar($_)}
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
And to get VABlAHMAdABQAGEAcwBzAHcAbwByAGQA I do this:
To Encode $EString means Encrypted String and $DString means Decrypted String
$EString = Read-Host "Type Text to Encode" -AsSecureString
$BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($EString)
$DString=[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$Encoded=[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($DString))
$Encoded # VABlAHMAdABQAGEAcwBzAHcAbwByAGQA
$DString # TestPassword
That way I can put any password I want on the script without too much hassle.
In case below code might help someone.
function Get-RemoteConnection()
{
//$serverIp can be passed as a parameter of the function.
//I just make it static for testing.
$serverIp = '192.168.100.137'
Enable-PSRemoting -Force
Set-Item wsman:\localhost\client\trustedhosts $serverIp
Restart-Service WinRM
#Set credentials needed for remote installation
$userName = "administrator"
$password = ConvertTo-SecureString "2020core0515" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList ($userName, $password)
$session = New-PSSession -ComputerName $serverIp -Credential $cred
$a = Invoke-Command -Session $session -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" | Select-Object DeviceID, #{label='UsedPercent'; expression={[Math]::Round((($_.size - $_.freespace) / $_.size) * 100, 2)}} }
Write-Host $a
return $session
}
function Delete-RemoteConnection($session)
{
Disconnect-PSSession $session | Out-Null
Disable-WSManCredSSP -Role Client
}
Instead of storing the encrypted password in a text file you can store it in the credential vault.
In the follwing example the user is prompted for a password only the first time, after that the password is retrieved from the credential vault.
# Load the Windows Runtime Class
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
$Vault = New-Object Windows.Security.Credentials.PasswordVault
$RESOURCE = "myresource"
$USERNAME = "myuser"
try {
$credentials = $Vault.Retrieve($RESOURCE,$USERNAME)
$pwd = $credentials.Password | ConvertTo-SecureString -Key (1..16)
}
catch {
$pwd = Read-Host "please enter your password:" -AsSecureString
$Encrypted = ConvertFrom-SecureString -SecureString $pwd -Key (1..16)
$credentials = new-object -Type Windows.Security.Credentials.PasswordCredential -ArgumentList $RESOURCE,$USERNAME,$Encrypted
$Vault.Add($credentials)
}
$cred = New-Object System.Management.Automation.PsCredential($USERNAME,$pwd)
why dont you try something very simple?
use psexec with command 'shutdown /r /f /t 0' and a PC list from CMD.