Upgraded to PS3, System.Net.WebClient not working on some machines - powershell

I am running in a lab environment and need to automate about 50 machines. I am trying to recover an .xml wireless network profile from a server then install it. This command is being sent from 1 server to the 50 clients.
I recently reimaged my some of my clients and upgraded from PS2 to PS3 and now my Download script is not working anymore.
It does work fine on my PS2 workstations. I am assuming that it might be a permission thing, but I'm not sure. ThrustedHosts is set to * and Script execution policy is set to Unrestricted.
Here's a snippet and the error:
function InstallProfile(){
clear
$fonction =
#'
param($profileName)
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
Write-Host (hostname)
$webclient.DownloadFile($uri, $File)
write-host (hostname) (netsh wlan add profile filename="c:\profiles\profile.xml")
'#
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
ExecCmd -fonction $fonction -argument $profileName
func_done
}
#
function ExecCmd
{
param(
$fonction,
$argument
)
$PingTest = RetrieveStatus
$results = #{}
$results = $PingTest.up
$results | sort -uniq | out-Null
$fonctionSB = ConvertTo-ScriptBlock($fonction)
foreach($result in $results)
{
$os = "Windows"
try{
$session = New-PSSession -ComputerName $result.address -Credential $credentials -EA stop
}
catch{
$os = "Not Windows"
}
if($os -eq "Windows"){
Invoke-Command $result.address -ScriptBlock $fonctionSB -Arg $argument -Credential $credentials
Get-PSSession | Remove-PSSession
}
else{
Write-Host $result.address "does not support Powershell commands"
}
}
}
And the error:
Exception calling "DownloadFile" with "2" argument(s): "An exception occurred during a WebClient request."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
+ PSComputerName : 192.168.2.110

I found a workaround,
I added a try/catch for the webclient.Download, which works for PS2. In the catch portion, I replaced the command with Invoke-WebRequest $uri -OutFile $File
Works fine for PS2 and PS3 this way!

Related

How to trigger a bat file on remote server only after the pssession is formed on the server

Write-Host "Welcome to Application Process Start/Stop Dashboard" -ForegroundColor Green
Write-Host "`n" "1) Stop" "`n" "2) Start"
[int]$resp = Read-Host "Choose option 1 or 2 for stopping or starting application process respectively"
if($resp -eq 1)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$result = [System.Windows.Forms.MessageBox]::Show('Are you sure you want to STOP ?', "Info" , 4 )
if ($result -eq 'Yes')
{
$user = "NAmarshmellow"
$server = "Desktop_10U"
$storesess = New-PSSession -ComputerName $server -Credential $user
Enter-PSSession -Session $storesess
$path = "\\Users\mellow\Documents\Proj"
$pwd = Read-Host -AsSecureString
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)
$value = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
NET USE $path /user:$user $value
Start-Process cmd -ArgumentList "/C C:\Users\Desktop_10U\Documents\Some\Stop.bat" -Wait
Clear-Variable storesess
Exit-PSSession
}
}
I want to trigger a bat file which has some commands that will stop a specific application process. To stop this application process there are specific commands which requires triggering the cmd file on a network drive. So I have written a code which will form PSSession and after the PSSession is formed only then the NET USE command should run. If I first form the PSSession and then trigger the command manually fire the NET USE command then it works fine. But when I trigger the code as a whole it doesn't run fine it throws below error.
NET : System error 1219 has occurred.
At line:18 char:1
+ NET USE $path /user:$user $value
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (System error 1219 has occurred.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared
resource and try again.
The issue is that Enter-PSSession only works via an interactive prompt. aka. You typing in commands into a prompt. A script/running everything together is not interactive (i.e. you can't start entering in commands into the middle of the running script).
The work around is to use Invoke-Command and place everything you want to perform in a script block. This way can be executed as non-interactive commands. e.g.:
....
$user = "NAmarshmellow"
$server = "Desktop_10U"
$path = "\\Users\mellow\Documents\Proj"
$pwd = Read-Host -AsSecureString
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)
$value = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
$script = {
$user = $Using:user
$path = $Using:path
$value = $Using:value
NET USE $path /user:$user $value
Start-Process cmd -ArgumentList "/C C:\Users\Desktop_10U\Documents\Some\Stop.bat" -Wait
}
Invoke-Command -ComputerName $server -Credential $user -ScriptBlock $script
....

Powershell Invoke-Command causes different result

I have a function that is used to purge a message queue on a machine but I'm looking to adapt it and make it a little more robust. I'd like to be able to fire the command to a machine even if the current machine doesn't have MSMQ installed.
The local command works without issue but when the invoke-command is called, the check to see if the queue exists returns false (even though the queue does exist). Has anyone run into anything like this before? Any suggestions?
This is my function:
Function Purge-MessageQueue
{
<#
.Synopsis
Use hostname to purge message queue
.DESCRIPTION
Checks if MSMQ is locally installed otherwise fire the purge
command to the machine you are purging
.EXAMPLE
Purge-MessageQueue -targetMachine $env:computername -queueName 'test'
#>
Param
(
[Parameter(Mandatory=$true)]
[String]$targetMachine,
[Parameter(Mandatory=$true)]
[string]$queueName
)
Write-Verbose "Purging $queueName queue on: $targetMachine"
$queueName = "$targetMachine\$queueName"
$error.clear()
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
try {[void][System.Messaging.MessageQueue]::Exists($queueName)}
catch
{
if ($_.exception.ToString() -like '*Message Queuing has not been installed on this computer.*')
{
#push command to machine
$RemoteSuccess = Invoke-Command -ComputerName $targetMachine -ScriptBlock { Param($queueName)
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{$queue.Purge()}
Catch{$error}
}
} -ArgumentList $queueName
}
}
If(!$error)
{
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
$queue.Purge()
}
}
If(!$Error -and !$RemoteSuccess)
{
Write-Host "$queueName queue on $targetMachine cleared"
}
Else
{
Write-Warning "Failed locating queue $queueName on $targetMachine"
}
}
NOTES:
In order to identify what exactly is going on, I used write-host on the exists statement and it returns false. The queue is not being found when I pass the scriptblock. It is executing on the other machine (tested writing a file which succeeded). When I run:
Write-Host "$([System.Messaging.MessageQueue]::Exists($queueName))`n$queueName"
$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
I get the false, the correct queue name, and the following error:
Exception calling "Purge" with "0" argument(s): "The queue does not
exist or you do not have sufficient permissions to perform the
operation."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : MessageQueueException
+ PSComputerName : XXXXXX
Running the same command directly on the machine works without issue.
I also found someone else trying to do something similar on serverfault:
https://serverfault.com/questions/399178/how-to-retrieve-names-of-all-private-msmq-queues-efficiently
And when I try this:
Invoke-Command -ComputerName $targetMachine -ScriptBlock { Get-MsmqQueue }
I get the following result:
Cannot find specified machine.
+ CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException
+ FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand
This following command does return the data, but it doesn't allow me to send a purge command:
Invoke-Command -ComputerName $targetMachine -ScriptBlock {Get-WmiObject -class Win32_PerfRawData_MSMQ_MSMQQueue}
I also tried to write the content to a script file and then call the file, which when run on the machine, works without issue but not when called via invoke-command:
$filewriter = #"
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists('$queueName'))
{
`$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{`$objqueue.Purge()}
Catch{`$error}
}
"#
$session = New-PSSession -ComputerName $targetMachine
Invoke-Command -Session $session -ScriptBlock {Param($FileWriter)
$FileWriter | Out-File "C:\Temp\PurgeQueue.ps1"
} -ArgumentList $filewriter
$test = Invoke-Command -Session $session -scriptblock {Pushd "C:\Temp\"
.\PurgeQueue.ps1}
I have not found the cause for this, but I will summarize what I have found and my workaround
Summary:
When invoking msmq commands via invoke-command, only private queues appear and can be manipulated.
Workaround:
I've build a function to deal with purging and adding message to queues by creating scheduled tasks on the remote machine to call the script created by the command.
Function Push-MSMQRemoteCommand
{
Param(
[Parameter(Mandatory=$true)]
$targetMachine,
[Parameter(Mandatory=$true)]
$password,
[Parameter(Mandatory=$true)]
$queueName,
[Switch]$purge,
$user,
$message)
Begin
{
If(!$user){$user = "$env:USERDOMAIN\$env:USERNAME"}
If($purge -and $message.Length -ne 0){Write-Error "Choose to purge or add... not both" -ErrorAction Stop}
$queuepath = "$targetMachine\$queueName"
#build commands to push
If($purge)
{
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If ([System.Messaging.MessageQueue]::Exists('$queuePath')) {
`$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queuePath
`$queue.Purge()
}
"#
}
ElseIf($message)
{
If($message.Length -eq 0){Write-Error "No message provided to add message" -ErrorAction Stop}
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
`$queue = new-object System.Messaging.MessageQueue "$queuepath"
`$utf8 = new-object System.Text.UTF8Encoding
`$msgBytes = `$utf8.GetBytes('$message')
`$msgStream = new-object System.IO.MemoryStream
`$msgStream.Write(`$msgBytes, 0, `$msgBytes.Length)
`$msg = new-object System.Messaging.Message
`$msg.BodyStream = `$msgStream
`$msg.Label = "RemoteQueueManagerPowershell"
`$queue.Send(`$msg)
"#
}
#Push Commands
Invoke-Command -ComputerName $targetMachine -ScriptBlock {
Param($user,$password,$scriptblock)
$scriptblock | Out-file -FilePath "C:\temp\ManageQueue.ps1" -Force
$action = New-ScheduledTaskAction -execute 'powershell.exe' -Argument '-File "C:\temp\ManageQueue.ps1"'
#scheudling action to start 2 seconds from now
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 2))
Register-ScheduledTask -TaskName RemoteQueueManager `
-Action $action `
-Trigger $trigger `
-User "$user"`
-Password $password
#Start-Sleep -Seconds 10
Unregister-ScheduledTask -TaskName RemoteQueueManager -Confirm:$false
Remove-Item -Path "C:\temp\ManageQueue.ps1" -Force
} -ArgumentList $user,$password,$scriptblock
}
}
From your analysis I have feeling that it is issue of rights.
Did you check the rights for your user?
If you are a normal user you have to do the following (not an Administrator) on the
destination computer/server/VM:
1) first create a group and add there users
net localgroup "Remote PowerShell Session Users" /add
net localgroup "Remote PowerShell Session Users" the-user /add
2) Invoke GUI
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
3) Add Remote PowerShell Session Users group and grant it execute (invoke) rights
4) Restart the service:
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
5) the user now should be able to run remote session
The original source is here.

Powershell - File path, is not a recognized as the name of a cmdlet. function script file

I am getting the following error,
Errors caught - TRAPPED: System.Management.Automation.RemoteException with message TRAPPED: The term 'D:\ServiceNow\RDC-
Dev-All\agent\scripts\PowerShell\ImMigration_script.ps1' is not recognized as the name of a cmdlet, function, script fil
e, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and t
ry again.
The issue appears to be with the invoke-command
Invoke-Command -Session $Session -ScriptBlock $theCommand2
i have tired using -FilePath with no luck.
Also tired passing the command and param separately :
Invoke-Command -Session $Session -ScriptBlock $theCommand2 -argumentlist $leName
I am triggering the script using:
D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell\invokLyncUAdd.ps1 -param1 'CN=lync2013testuser1,CN=Users,DC=test,DC=COMPANY,DC=com' -param2 AD\sys-LyncProATSC -param3 Z0185-XAP0007-S.test.COMPANY.com
###############################################################################
param( $param1, $param2, $param3 )
$ErrorActionPreference = "Stop"
# trap {
# write-output $("TRAPPED: " + $_.Exception.GetType().FullName);
# write-output $("TRAPPED: " + $_.Exception.Message);
# break
#}
$leName = $param1
$leName = ("'" + "$leName" + "'")
$thePath = 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell'
$theCommand = $thePath+"\ImMigration_script.ps1 -param1 $leName"
$theCommand2 = [Scriptblock]::Create($theCommand)
# Write-Host "We use string $theCommand below"
$Account = $param2
$useP = Get-Content $thePath\'Information.txt'
$Prompt = convertto-securestring $useP -AsPlainText -Force
$leHost = $param3
try{
$Credential = new-object -typename System.Management.Automation.PSCredential
-argumentlist $Account, $Prompt
$Timeout = New-PSSessionOption -IdleTimeout 60000
$Session = New-PSSession -ComputerName $leHost -Credential $Credential -
Authentication Credssp -SessionOption $Timeout -ErrorAction Stop
Invoke-Command -Session $Session -ScriptBlock $theCommand2
}
catch
{
$exceptType = $("TRAPPED: " + $_.Exception.GetType().FullName);
$exceptMess = $("TRAPPED: " + $_.Exception.Message);
}
finally
{
if($exceptType) { "Errors caught - $exceptType with message $exceptMess " } }
Any help would be great, Thanks
The session is being executed on the remote computer, and I believe that's where PowerShell will expect the file to exist.
I would approach it by attempting to load the local script as a scriptblock so that it is in memory:
$thePath = 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell'
$theCommand = $thePath+"\ImMigration_script.ps1"
$theCommand2 = [Scriptblock]::Create(Get-Content $theCommand)
Then, from your question:
Invoke-Command -Session $Session -ScriptBlock $theCommand2 -argumentlist $leName
Please let me know if this works.
If the file is in local, then
powershell.exe -noexit -file 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell\invokLyncUAdd.ps1' -param1 'CN=lync2013testuser1,CN=Users,DC=test,DC=COMPANY,DC=com' -param2 'AD\sys-LyncProATSC' -param3 'Z0185-XAP0007-S.test.COMPANY.com'
If It is in the remote system, then make sure you are mentioning the remote path properly in the invoke-command.

How to remotely delete an AD-Computer from Active Directory - Powershell

I am running Powershell on a remote computer that is not connected to the domain, does not have any modules and is running PS 2.0.
I want to contact the Active Directory of my domain, check if there is an entry for this computer and; if yes, delete that entry.
Checking the AD via ADSI for existance of the computer is easy. However the deleting does not work somehow.
Here is my code so far:
# Variables
$domain = "Test.com"
$Ldap = "LDAP://$domain"
$Global:AdsiSearcher = $Null
# Function to Delete PC
Function DeleteThisPc ()
{
$CurrentSearch = $Global:AdsiSearcher
$One = $CurrentSearch.FindOne()
$OPath = [adsi]$One.Path
$OPath.psbase.DeleteTree()
The Problem lies here. Even though $OPath is of type System.DirectoryServices.DirectoryEntry and the propertylist shows all properties, it does not allow me to delete the object.
Exception calling "DeleteTree" with "0" argument(s): "Logon failure:
unknown user name or bad password.
At C:\TEMP\Domjoin1.1.ps1:49 char:33 $OPath.psbase.DeleteTree <<<< ()
CategoryInfo: NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : DotNetMethodException
Code:
# Function to get a ADSISearcher and set it to the global-AdsiSearcher
Function ConnectAD ()
{
$domain = new-object DirectoryServices.DirectoryEntry($Ldap,"$domain\Bob",'1234')
$filter = "(&(objectCategory=computer)(objectClass=computer)(cn=$ComputerName))"
$AdsiSearch = [adsisearcher]""
$AdsiSearch.SearchRoot = $domain
$AdsiSearch.Filter = $filter
$Global:AdsiSearcher = $AdsiSearch
}
# Main Function
Function Sub_Check-ADComputer()
{
ConnectAD
$CurSearch = $Global:AdsiSearcher.findOne()
if($CurSearch -ne $null)
{
DeleteThisPc
}
}
# Start
Sub_Check-ADComputer
Even though the issue seems to be obvious as the error states:
Logon failure: unknown user name or bad password.
The username and password is the same that I use to get the object from the AD in the first place. So it does work - do I somehow have to give the credentials again when trying to deleteTree() ? I also gave the User FullControl on the OU that the object is stored in.
Edit:
When I do it on another machine with PS 3.0 I get a different Error message:
Exception calling "DeleteTree" with "0" argument(s): "Access is
denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"
I found the problem.
When using invoke command the variables are not transmitted unless specified by -argumentlist. Another approach I discovered was the following, which is the one I am using now and which works like a charm.
$domain = "DOMAINNAME"
$AdUser = "$domain\JoinDom"
$AdPW = "PASSWORD"
$AdPass = convertto-securestring -string $AdPW -AsPlainText -Force
$AdCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $AdUser,$AdPass
$ThisComputer = $Env:COMPUTERNAME
$RetValue = $true
Function CheckExist ()
{
$ErrorActionPreference = ‘SilentlyContinue’
$Ascriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("get-adcomputer $ThisComputer")
$Ret = Invoke-Command -ComputerName SERVERNAME -ScriptBlock $Ascriptblock -Credential $AdCred
$ErrorActionPreference = ‘Continue’
return $Ret
}
$ExistBefore = CheckExist
if($ExistBefore -ne $null)
{
$scriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("Remove-ADComputer $ThisComputer")
Invoke-Command -ComputerName SERVERNAME -ScriptBlock $scriptblock -Credential $AdCred
$ExistAfter = CheckExist
if($ExistAfter -ne $null){$RetValue = $false}
}
if($RetValue -ne $false)
{
Add-computer -domainname $domain -credential $Adcred -OUPath "OU=MyOU,DC=DOMAIN,DC=DE"
Restart-Computer -Force
}
If your domain controller runs Windows Server 2008 or higher you could leverage PowerShell sessions to avoid having to work with ADSI.
Just run the following command:
Enter-PSSession -ComputerName domaincontroller.test.com -Credential (Get-Credential)
Then run Import-Module ActiveDirectory to allow you to use Get-ADComputer and Remove-ADComputer.

Powershell - Some commands won't run with Invoke-Command

I am trying to send some commands from a server to about 50 clients running Powershell. Most commands work using Invoke-Command. I used the exact same format as my other commands, yet this one won't work. Basically I want to have each client fetch an .xml file from my server to import it later on. I am missing $credentials and other variables from my code sample here but they are setup correctly somewhere else in my script.
Permission wise, TrustedHosts in winrm is set to * and script execution is set to Unrestricted.
clear
$temp = RetrieveStatus
$results = $temp.up #Contains pinged hosts that successfully replied.
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
$command = {write-host (hostname) $webclient.DownloadFile($uri, $File)}
foreach($result in $results)
{
# download profile from C:\share\profiles
Invoke-Command $result.address -ScriptBlock $command -Credential $credentials
# add profile to wireless networks
# Invoke-Command $result.address -ScriptBlock {write-host (hostname) (netsh wlan add profile filename="c:\profiles\$args[0].xml")} -argumentlist $profileName -Credential $credentials
}
I get the following error:
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (DownloadFile:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Any idea? The same command works flawlessly on the clients when run locally.
You use $webclient within a scriptblock where $webclient will not be defined on the other end. Why don't you create the web client in the scriptblock e.g.:
$command = {
param($profileName)
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
Write-Host (hostname)
$webclient.DownloadFile($uri, $File)}
}
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
Invoke-Command $result.address -ScriptBlock $command -Credential $credentials -Arg $profileName
This will require you to provide some of the variables from the client to the remote machine via the -ArgumentList parameter on Invoke-Command. Those supplied arguments then map to the param() statement in the scriptblock.