To me if I am able to start a service on a remote machine when referencing them from a text file I should be able to manipulate registry keys. But I am not sure why I'm not able to do so. What am I doing wrong here? The StartWinRM works on remote systems and it enables it without any issues. When I ran this it creates the keys local to my machine. Why is this?
$Computers = Get-Content -Path 'D:\HomeFolder\MachineNames.txt'
foreach ($computer in $Computers) {
If (test-connection -ComputerName $computer -Count 1 -Quiet) {
Try {
Set-Service -ComputerName $computer -Name WinRM -StartupType Automatic -ErrorAction SilentlyContinue
StartWinRM -Computer $computer
Invoke-Command -ComputerName $computer -ScriptBlock {
New-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\" -Name Name1 -PropertyType String -Value 0 -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\" -Name Name2 -PropertyType String -Value "Description" -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\" -Name Name3 -PropertyType String -Value 1 -Force
} -Credential $cred
$status = "Success"
}
Catch {
$status = "Failed"
}
}
}
I get this error:
Connecting to remote server ComputerName failed with the following error message : The WinRM client cannot process the request. If the
authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine
must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be
authenticated.
If anyone interested, I fixed my problem:
$computers = Get-Content "D:\HomeFolder\MachineNames.txt"
foreach ($computer in $computers){
if (Test-Path -Path "\\$computer\d$\<some_older_existing_folder>"){
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computer )
$regKey= $reg.OpenSubKey("SOFTWARE\\WOW6432Node",$true)
$regKey.SetValue("Key1","0",[Microsoft.Win32.RegistryValueKind]::String)
$regKey.SetValue("Key2","1",[Microsoft.Win32.RegistryValueKind]::String)
$regKey.SetValue("Key3","1",[Microsoft.Win32.RegistryValueKind]::String)
}
Write-Host "Keys already exist" -ForegroundColor Yellow
}
Related
I need to copy a file to multiple computers, but can only do so if a particular app (process) is not running.
I know I can use Invoke-Command to run a script (scriptblock) on a list of machines.
But how can I check if process is running on the machine and then only copy file if it is not running.
So that at the end of running against a load of computers I can easily see those which succeeded e.g. process was not running and file was copied
Thanks
UPDATE:
I am assuming something like this will do the first bits of what I am asking, but how to visually show or log success or failure so I know which computers have been done - doesn't need to be anything fancy, even if simply a variable that holds computername of those where process wasn't running and file was copied okay
Invoke-Command -ComputerName PC1, PC2, PC3 -ScriptBlock {
If ((Get-process -Name notepad -ea SilentlyContinue) -eq $Null){
Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "C:\test\file.txt" -Force
}
}
$Procs = invoke-command -ComputerName PC1 { get-process | Select Name }
If($Procs -notmatch "Notepad"){ Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$PC1\c$\test\" -Force}
edited:
$computers = #("PC1","PC2","PC3")
Foreach($computer in $computers){
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){"$Computer - not running Notepad"; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){"$Computer - is running Notepad"}
}
Edit2(for clean output):
$computers = #("PC1","PC2","PC3")
$RNote = #()
$NNote = #()
$off = #()
Foreach($computer in $computers){
$TestC = Test-Connection -ComputerName $computer -Count 1
If(!($TestC)){$off += $computer} Else{
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){$NNote +=$computer; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){$RNote +=$computer}
}
}
$leng =[array]$RNote.count,$NNote.Count,$off.count
[int]$max = ($leng | measure -Maximum).Maximum
for($i=0; $i -lt $max;$i++){
[pscustomobject]#{
"Notepad On" = $(if ($RNote[$i]){$RNote[$i]})
"Notepad Off" = $(if ($NNote[$i]){$NNote[$i]})
"Offline " = $(if ($off[$i]){$off[$i]})
}
}
I think this is what you're looking for or at least close:
$Results = #()
$Results +=
Invoke-Command -ComputerName DellXPS137000, DellXPS8920 -ScriptBlock {
$GPArgs = #{Name = "Notepad++"
ErrorAction = "SilentlyContinue"}
If ( $Null -ne (get-process #GPArgs )) {
#Process your copy here
$Status = "Success"
}
Else {$Status = "Failed"}
$Machine =
(Get-CimInstance -ClassName 'Win32_OperatingSystem').CSName
Return ,"$Machine : $Status"
}
Value of $Results:
PS> $results
DELLXPS137000 : Success
DELLXPS8920 : Failed
HTH
I am working on this command to be able to view and edit a registry key remotely to a computer on a joined domain when I need to test something. In this case, I am looking at Excel's "vbawarninsg" key. This works just fine.
cls
$computername = Read-Host "Enter computer name..."
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
The next part is to set a new value for the "vbawarnings" key using New-ItemProperty. When I assigned a variable for the -Path name it gives me an error "Cannot bind argument to parameter 'Path' because it is null."
This is the script that gives me an error
cls
$computername = Read-Host "Enter computer name..."
$registryPath = 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security'
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
$value = Read-Host "To modify...Enter a value"
New-ItemProperty -Path $registryPath -Name $name -Value $value `
-PropertyType DWORD -Force -Verbose | Out-Null
Any help is greatly appreciated!
In order to use a variable remotely (such as the case with Invoke-Command), you need to use the $using: variable scope:
Invoke-Command -ComputerName $cn {
Get-ItemProperty -Path $using:regPath
}
or pass it as a parameter:
Invoke-Command -ComputerName $cn {
param($path)
Get-ItemProperty -Path $path
} -ArgumentList '-path', $regPath
See this article
When you do Invoke-Command, that scriptblock gets sent to the remote server.
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath }
On the remote server, $registryPath is null even though you have it locally in your script.
So just hardcode the registry path instead:
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' }
Starting to write powershell scripts (very new) because SCCM tends to respond better to them (both client and server)
So with the above stated here is my first script:
#Changes the 'ProvisioningMode' Key in the registry to False
$ProvisiongMode = New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
#Clears or 'nulls' the SystemTaskExcludes key in the registry
$SystemTaskExludes = New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
#----------------------------------------------------------------------------------------------
$Success = "C:\Path\to.log"
$Failure = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
$SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode
$Online = Test-Conntection -Computername $ComputerName -Count 1 -Quiet
ForEach ($ComputerName in $Computers)
if ($Online -eq 'False')
{
Write-Output $ComputerName`t'Connection Failed' >> $Failure
}
Else
{
if ($SearchStr -eq True)
{
$ProvisioningMode
$SystemTaskExcludes
}
}
#Second Check
if ($SearchStr -eq 'False')
{
Write-Output $ComputerName`t'Registry has been changed' >> $Success
}
The issue in question is the $Online variable. I would like to see if a computer is responsive to ping, if true then proceed to run $ProvisioningMode and $SystemTaskExclude.
Then the other issue is querying that key to see if it changed. The issue with that one is $SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode returns
ProvisionMode
-----------------
False
And I cant grab just the false data.
Like I stated; very new at powershell and writing something that I will use helps me learn.
Edit: What I Have tried is
ForEach ($Name in $Computers)
{
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet | Write-Output $Online
}
if ($Online -eq 'True') {Write-Output $Name`t'Computer is online' >> C:\Online.txt}
And many variations of the same thing.
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
Returns Data, which is what I want, but I need to input that into an If statement and still retain the $Name for the $StringStr and log files.
Those of you wondering, this takes the client out of provisioning mode when running an OSD. It fixes the 'No Self-Signed Certificate' issue.
Even though the string representations of boolean values in PowerShell are True and False, the correct way to compare againt such a value is with the $true and $false variables.
Furthermore, assign the result of Test-Connection to $Online with =:
$Online = Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
if($Online -eq $true){
# Machine responds to ping, do stuff!
}
But the comparison is actually unnecessary. If $Online already equals $frue or $false, you can use it on its own inside the if statement:
if($Online){
# Machine responds to ping, do stuff!
}
I assume that $ProvisionMode, $SystemTaskExcludes and $SearchStr are all statements that you want to execute on the remote machine, not on the SCCM server itself.
To do so, you will need to connect to the machine and instruct it to execute the *-ItemProperty statements.
# Enclosing statements in {} creates a ScriptBlock - a piece of code that can be invoked later!
$ProvisionMode = {
#Changes the 'ProvisioningMode' Key in the registry to False
New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
}
$SystemTaskExludes = {
#Clears or 'nulls' the SystemTaskExcludes key in the registry
New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
}
$SearchStr = {
Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | Select-Object -ExpandProperty ProvisioningMode
}
#----------------------------------------------------------------------------------------------
$LogFilePath = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
foreach($Computer in $Computers){
$Online = Test-Connection -Computername $Computer.Name -Count 1 -Quiet
if(-not $Online)
{
"$ComputerName`t'Connection Failed'" | Out-File -FilePath $LogFilePath -Append
}
else
{
$SearchResult = Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SearchStr
if ($SearchResult)
{
# The call operator (&) invokes the scriptblock
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $ProvisionMode
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SystemTaskExludes
}
else # SearchStr must be $false, or non-existing
{
"$ComputerName`t'Registry has been changed'" | Out-File -FilePath $LogFilePath -Append
}
}
}
For simplicity, I've used Invoke-Command with the -ComputerName parameter, but in a real world situation, I would set up a PSSession with New-PSSession, and reuse that for the connection with Invoke-Command -Session
If I run the command from with in the script
reg add "HKU\$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v AutoConfigURL /t REG_SZ /d "http://mydomaon.com/proxies/proxy.pac" /f
it works perfectly, because it is local.
When I try to change the same registry key on a remote machine and changing the line to this
reg add "\\$machinename\HKU\$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v AutoConfigURL /t REG_SZ /d "http://mydomaon.com/proxies/proxy.pac" /f
It fails with
ERROR: reg : ERROR: Invalid key name. + CategoryInfo : NotSpecified: (ERROR: Invalid key name.:String) [], RemoteException
ERROR: + FullyQualifiedErrorId : NativeCommandError
ForEach ($machine in $machines)
{
$User = New-Object System.Security.Principal.NTAccount($env:UserName)
$sid = $User.Translate([System.Security.Principal.SecurityIdentifier]).value
New-PSDrive HKU Registry HKEY_USERS
# Get-Item "HKU:\${sid}"
Set-Location HKU:\
cd HKU:\
reg add '$SID\Software\Microsoft\Windows\CurrentVersion\Internet Settings' /v AutoConfigURL /t REG_SZ /d 'http://proxy.domain.com/proxies/proxy.pac' /f
}
Here is an approach that should work (also better then using reg.exe):
Local Computer:
New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS
New-ItemProperty -Path "HKU:\$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name AutoConfigURL -PropertyType String -value "http://proy.domain.com/proxies/proxy.pac" -Force
Remote Computer:
$credential = Get-Credential
Foreach($machine in $computers){
Invoke-Command -ComputerName $machine -Credential $credential -ScriptBlock {$temp = $args[0];
New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS; New-ItemProperty -Path "HKU:\$temp\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -PropertyType String -Name AutoConfigURL -Value "http://proy.domain.com/proxies/proxy.pac" -Force} -argumentlist $sid
}
Also here are the Type mappings for New-Itemproperty´s -PropertyType:
REG_SZ = String
REG_DWORD = DWord
REG_QWORD = QWord
REG_MULTI_SZ = MultiString
REG_BINARY = Binary
So after a few problems here is an updated version which should do everything including determining the local users sid by username (added linebreaks for readability, remove them when you copy)
$username = "yourUsername"
$credential = Get-Credential
Foreach($machine in $computers){
Invoke-Command -ComputerName $machine -Credential $credential -ScriptBlock {$temp = $args[0];
New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS;
$sid = (Get-CimInstance -Class Win32_UserAccount | where {$_.Name -eq $temp} | select SID).SID;
New-ItemProperty -Path "HKU:\$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -PropertyType String -Name AutoConfigURL -Value "http://proy.domain.com/proxies/proxy.pac" -Force
} -argumentlist $username
}
After furhter discussion with Kent and clearly specifying the environment (Windows XP domain-joined machines, user logged on) the script has to run in this was what we came up with:
(this works only if the user is logged in on XP)
$sid = ((get-aduser $username).SID).Value
$credential = Get-Credential
Foreach($machine in $computers){
Invoke-Command -ComputerName $machine -Credential $credential -ScriptBlock {New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS; New-ItemProperty -Path "HKU:\$($args[0])\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -PropertyType String -Name AutoConfigURL -Value "http://proxy.domain.com/proxies/proxy.pac" -Force} -argumentlist $sid
}
This is how it would work if the user was not currently log in on XP:
$defaultprofilepath = "C:\documents and settings"
$username = "username"
$profilepath = "$defaultprofilepath\$username"
$credential = Get-Credential
Foreach($machine in $computers){
Invoke-Command -ComputerName $machine -Credential $credential -ScriptBlock {reg load "HKU\TempDir" "$($args[0])\NTUSER.DAT"; reg add "HKU\TempDir\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v AutoConfigURL /t REG_SZ /d 'http://proxy.domain.com/proxies/proxy.pac' /f; reg unload "HKU\TempDir"} -argumentlist $profilepath
}
P.S. If someone has a clue if there is a powershell cmdlet to load registry hives please comment, i could not find one on the fly
Using WMI you could do it like this:
$computers = ...
$user = New-Object Security.Principal.NTAccount($env:USERNAME)
$sid = $user.Translate([Security.Principal.SecurityIdentifier]).Value
$subkey = "$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
$url = 'http://proy.domain.com/proxies/proxy.pac'
$HKU = [convert]::ToUInt32('0x80000003', 16)
$credential = Get-Credential
foreach ($machine in $computers) {
$reg = Get-WmiObject -List -Namespace 'root/default' -Computer $machine `
-Credential $credential | ? { $_.Name -eq 'StdRegProv' }
$reg.SetStringValue($HKU, $subkey, 'AutoConfigURL', $url) | Out-Null
}
Note, however, that for remote WMI access you need administrative privileges on the remote host. Without admin privileges you could use Invoke-Command for local WMI access on the remote host:
foreach ($machine in $computers) {
Invoke-Command -Computer $machine -Credential $credential -ScriptBlock {
$reg = Get-WmiObject -List -Namespace 'root/default' |
? { $_.Name -eq 'StdRegProv' }
$reg.SetStringValue($args[0], $args[1], 'AutoConfigURL', $args[2]) | Out-Null
} -ArgumentList $HKU, $subkey, $url
}
Note also that only domain accounts have the same SID accross hosts. For local accounts you need to determine the SID of the account on the remote host:
foreach ($machine in $computers) {
Invoke-Command -Computer $machine -Credential $credential -ScriptBlock {
$user = New-Object Security.Principal.NTAccount($args[0])
$sid = $user.Translate([Security.Principal.SecurityIdentifier]).Value
$subkey = "$sid\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
$reg = Get-WmiObject -List -Namespace 'root/default' |
? { $_.Name -eq 'StdRegProv' }
$reg.SetStringValue($args[1], $subkey, 'AutoConfigURL', $args[2]) | Out-Null
} -ArgumentList $env:USERNAME, $HKU, $url
}
If the account in question is a domain account, you may want to provide the domain name when creating the NTAccount object, because otherwise you'd be getting the wrong account/SID when there's a local account with the same name as the domain account.
$user = New-Object Security.Principal.NTAccount($env:USERDOMAIN, $env:USERNAME)
$sid = $user.Translate([Security.Principal.SecurityIdentifier]).Value
I have the below script that I want it to go out to multiple servers and get the value of a registry. Unfortunately it is currently just posting back the local registry value of the machine that I am running the script on.
How do I get the script to run against remote registry?
SCRIPT:
clear
#$ErrorActionPreference = "silentlycontinue"
$Logfile = "C:\temp\NEWnetbackup_version.log"
Function LogWrite
{
param([string]$logstring)
Add-Content $Logfile -Value $logstring
}
$computer = Get-Content -Path c:\temp\netbackup_servers1.txt
foreach ($computer1 in $computer){
$Service = Get-WmiObject Win32_Service -Filter "Name = 'NetBackup Client Service'" -ComputerName $computer1
if (test-connection $computer1 -quiet)
{
$NetbackupVersion1 = $(Get-ItemProperty hklm:\SOFTWARE\Veritas\NetBackup\CurrentVersion).PackageVersion
if($Service.state -eq 'Running')
{
LogWrite "$computer1 STARTED $NetbackupVersion1"
}
else
{
LogWrite "$computer1 STOPPED $NetbackupVersion1"
}
}
else
{
LogWrite "$computer1 is down" -foregroundcolor RED
}
}
You can try using .net:
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computer1)
$RegKey= $Reg.OpenSubKey("SOFTWARE\\Veritas\\NetBackup\\CurrentVersion")
$NetbackupVersion1 = $RegKey.GetValue("PackageVersion")
Try the Remote Registry Module, the registry provider cannot operate remotely:
Import-Module PSRemoteRegistry
Get-RegValue -ComputerName $Computer1 -Key SOFTWARE\Veritas\NetBackup\CurrentVersion -Value PackageVersion
If you have Powershell remoting and CredSSP setup then you can update your code to the following:
$Session = New-PSSession -ComputerName $Computer1 -Authentication CredSSP
$NetbackupVersion1 = Invoke-Command -Session $Session -ScriptBlock { $(Get-ItemProperty hklm:\SOFTWARE\Veritas\NetBackup\CurrentVersion).PackageVersion}
Remove-PSSession $Session
another option ... needs remoting ...
(invoke-command -ComputerName mymachine -ScriptBlock {Get-ItemProperty HKLM:\SOFTWARE\VanDyke\VShell\License -Name Version }).version
For remote registry you have to use .NET with powershell 2.0
$w32reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$computer1)
$keypath = 'SOFTWARE\Veritas\NetBackup\CurrentVersion'
$netbackup = $w32reg.OpenSubKey($keypath)
$NetbackupVersion1 = $netbackup.GetValue('PackageVersion')
If you need user's SID and browse remote HKEY_USERS folder, you can follow this script :
<# Replace following domain.name with yours and userAccountName with remote username #>
$userLogin = New-Object System.Security.Principal.NTAccount(“domain.name“,”userAccountName“)
$userSID = $userLogin.Translate([System.Security.Principal.SecurityIdentifier])
<# We will open HKEY_USERS and with accurate user’s SID from remoteComputer #>
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(‘Users’,”remoteComputer“)
<# We will then retrieve LocalName value from Control Panel / International subkeys #>
$key = $userSID.value+”\Control Panel\International”
$openKey = $remoteRegistry.OpenSubKey($key)
<# We can now retrieve any values #>
$localName = $openKey.GetValue(‘LocaleName’)
Source : http://techsultan.com/how-to-browse-remote-registry-in-powershell/