How to remotely change a service account password on multiple servers - powershell

I've been searching through the archives here but haven't quite found a simple (ie: something I can understand) solution to my problem. I am changing passwords on service accounts using powershell and wmi. I can change the services one at a time across all servers like so:
$Service = Get-WmiObject -Class Win32_Service -computer REMOTESERVER -filter "name='SERVICENAME'"
$service.change($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
As you can see, I can refer to whatever server I want and whatever service on that server. What I'd like to do is the following
Provide a list of servers (a text file with "REMOTESERVER1,REMOTESERVER2" or something similar
Change the password for multiple services on the same machine that are running under the same credentials. I was able to get a list of mutliple services using -filter "StartName LIKE '%\MYSERVICEACCOUNT'", but when I then try to run the $service.change to update the password, I get an error
Method invocation failed because [System.Object[]] doesn't contain a method named 'change'.
At line:1 char:16
+ $service.change <<<< ($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
+ CategoryInfo : InvalidOperation: (change:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
It works fine with only one service though
Restart services only if they are already running
How can I enhance this script to handle these 3 additional items?
Thank you

You would want to add a loop to your above script, and you would want
param
(
[string]$File
)
$Computer = Get-Content $file
foreach ($i in $Computer){
$Service = Get-WmiObject -Class Win32_Service -computer $i -filter "name='SERVICENAME'"
$service.change($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
}
You add the Param so you can utilize this many different times, then the foreach loop will run through your code for each computer in your .txt file.
Now this only answers the first part of your question but should give you a good starting point.

Related

Trying to Retrieve A Reg Value From Remote Machines Using Powershell

I am trying to retrieve a registry value from each computer using a for each loop and then output that value to a folder in a csv.
That part works fine. The part I am having an issue with is having powershell connect to the remote computers.
This is running internal only
I have admin rights across all workstations
Firewalls are configured to allow all traffic to pass
When I run this script I get this error for every workstation it tried to connect to:
Enter-PSSession : Connecting to remote server workstationX failed with
the following error message : WinRM cannot complete the operation.
Verify that the specified computer name is valid, that the computer
is accessible over the network, and that a firewall exception for the
WinRM service is enabled and allows access from this computer. By
default, the WinRM firewall exception for public profiles limits
access to remote computers within the same local subnet. For more
information, see the about_Remote_Troubleshooting Help topic. At
C:\Users\Rich_Ellis\Desktop\O365\O365Channels\O365Channel.ps1:5 char:2
+ {Enter-PSSession -ComputerName $Computer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (workstationX:String) [Enter-PSSession], PSRemotingTransportException
+ FullyQualifiedErrorId : CreateRemoteRunspaceFailed
My script is:
$Computers = Get-Content "C:\Users\Rich_Ellis\Desktop\O365\O365Channels\computers.txt"
foreach ($Computer in $Computers)
{Enter-PSSession -ComputerName $Computer
$key = 'HKLM:\SOFTWARE\Microsoft\Office\CLickToRun\Configuration'
(Get-ItemProperty -Path $key -Name CDNBaseUrl).CDNBaseUrl | Export-CSV -path "\\s00itstorage\OfficeChannel\$($env:COMPUTERNAME)-O365Channel03292018.csv"}
Any help would be appreciated. TIA
Molding a previous answer to your use-case:
$HKEY_LOCAL_MACHINE = 2147483650
$GwmiArgs = #{
Class = 'StdRegProv'
Namespace = 'Root\Default'
List = $True
}
ForEach ($Computer in #(Get-Content -Path 'C:\Users\Rich_Ellis\Desktop\O365\O365Channels\computers.txt'))
{
$GwmiArgs['ComputerName'] = $Computer
$Registry = Get-WmiObject #GwmiArgs
$Registry.GetStringValue(
$HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Office\ClickToRun\Configuration',
'CDNBaseUrl'
).sValue | Export-CSV -Path "\\s00itstorage\OfficeChannel\$Computer-O365Channel03292018.csv"
}
This uses wmi instead of psremoting to poll the information which may be easier to rely on as it is already configured/enabled on most PCs and can utilize IP addresses due to DCOM/RPC (psremoting only supports kerberos by default)
This solution can be further improved by using Invoke-WmiMethod instead of creating a wmi object for each poll, but I haven't done the work already for that!

Add-Printer with Network Printer fails

When I use Add-Printer -ConnectionName '\\server\Printer' from a local PowerShell environment it succeeds.
When I use the same command, but wrapped in an Invoke-Command like so:
Invoke-Command -ComputerName 'someServer' -Credential $creds -Authentication CredSSP -ScriptBlock { Add-Printer -ConnectionName '\\server\Printer' }
it fails saying
'\' is an invalid character.
I'm guessing it's connected to different levels of user profile being available locally vs remotely, but I'm not sure how to resolve it.
Edit: To update this, I've had to abandon this approach as I couldn't get it to work. My work-around has been to use
RUNDLL32 PRINTUI.DLL,PrintUIEntry /ga /c\\computerName /n\\someServer\Printer /q
Which works, though it does something slightly different to the Add-Printer cmdlet.
I'm not 100% sure why you're seeing that error. I'm sure it'll be something to do with how it's handling passing through the string.
However, you shouldn't really need to run it through Invoke-Command as Add-Printer has two options within itself to add printers to remote computers.
You can specify single machine with the -CompuerName parameter:
Add-Printer -ComputerName 'someServer' -ConnectionName '\\server\Printer'
You can also specify a (or multiple) CimSessions with the -CimSession parameter, allowing you to hit a bunch of machines at once.
A caveat with this command to be aware of is that it only works on Server 2012/ Windows 8 and above (including the remote target).
I ran into a similar problem, but I was trying to loop through a list of shared printers from a print server then add all of them to the local machine. Jump to the bottom if you want to see my solution.
The following returns all of the shared printers as objects:
$printerList = Get-Printer -ComputerName PrintServer | where Shared -eq $true
The following should have looped through all my printers and added each one:
foreach ($printer in $printerList) {
Add-Printer -ConnectionName "\\PrintServer\$printer.SharedName"
}
Instead, I supposedly run into the same error as the OP:
Add-Printer : The specified server does not exist, or the server or printer name is invalid. Names may not contain ',' or '\' characters.
At line:1 char:38
+ ... nterList) { Add-Printer -ConnectionName "\PrintServer\$printer.SharedNam ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (MSFT_Printer:ROOT/StandardCimv2/MSFT_Printer) [Add-Printer], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070709,Add-Printer
Needless to say, I'm very puzzled by those results because when I test what is going on, I see the following:
Write-Host $printer.ShareName
printerName
Great! Why doesn't it work in double quotes though? I am intentionally using double quotes to return the contents of the $printer variable:
Write-Host "$printer.ShareName"
MSFT_Printer (Name = "printerName", ComputerName = "PrintServer", DeviceType = 0, Type = 0).ShareName
Rather than mess with a CimException likely due to typecasting or who knows what (and yes, the ShareName property is a simple string already, it's not a hash table or a nested funky datatype of sorts), I did a simple ToString() as a workaround:
$printerList = Get-Printer -ComputerName PrintServer | where Shared -eq $true
foreach ($printer in $printerList) {
$printerStr = $printer.ShareName.ToString()
Add-Printer -ConnectionName "\\PrintServer\$printerStr"
}
I ran into this issue as well ToString() didn't resolve it for me. It was the "not required" parameter -ComputerName. I was running remove-PrinterPort -Name $port, which resulted in the following error:
The specified server does not exist, or the server or printer name is invalid. Names may not contain ',' or '\' characters.
After banging my head, and Googlin', I finally added the computer name as a test like this $env:computername, I tried using a "." before, because I thought that meant local computer but, it didn't work, just stalled.
After adding the computer name, the ports, and drivers were removed without error.
get-help Remove-PrinterPort shows that -Computer name is not required but, it does seem to be required:
-ComputerName [<String>]
Specifies the name of the computer from which to remove the printer port.
Required? false
Position? named
Default value none
Accept pipeline input? false
Accept wildcard characters? false

SCCM cmdlet from a remote PC

I'm trying to create a script to query members of a sccm2012 device collection so that I can pipe that into put into maintenance window. I need to run this from a remote machine.
Here's what I have - if I run each line by line it works fine.
But if I run it as a script continuously fails. If you could point me in the right direction please or solve for me. PS I'm not that good at PS but getting better.
PS F:\Install\Scritps> Enter-PSSession -ComputerName SERVER -ConfigurationName Microsoft.PowerShell32
Import-module "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1"
cd P01:\
Import-module : The specified module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1' was not loaded because no
valid module file was found in any module directory.
At line:4 char:1
+ Import-module "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConso ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (C:\Program File...ionManager.psd1:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
cd : Cannot find drive. A drive with the name 'P01' does not exist.
At line:6 char:1
+ cd P01:\
+ ~~~~~~~~
+ CategoryInfo : ObjectNotFound: (P01:String) [Set-Location], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.SetLocationCommand
I would guess that Enter-PSSession fails becauase it is designed for interactive use, and not to be used in a script. If that fails, then the following commands would fail because you're missing the SCCM 2012 Admin Console.
To run remote commands in a script, use Invoke-Command. Combine it with New-PSSession if you need a special configuration, different credentials etc.
$sess = New-PSSession -ComputerName SERVER -ConfigurationName Microsoft.PowerShell32
Invoke-Command -Session $sess -ScriptBlock {
Import-module "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1"
cd P01:\
gci
}
Powershell Equivalent to Query in SCCM UI
I'm guessing you'd like to script the same queries as SCCM allows you to specify in this device collection query rules property dialog box:
The most reliable way to run such a query on an SCCM server from powershell on a workstation is using CIM. The trick is figuring out how to run the query so that it is interpreted in exactly the same way as if SCCM itself runs the query. Doing it this way seems to achieve that goal:
$SiteCode = 'ST1' # replace this with your SCCM site code
$SccmServerName = 'sccm01' # replace this with your server name
# replace this with your query
$query = #'
select
SMS_R_SYSTEM.ResourceID,
SMS_R_SYSTEM.ResourceType,
SMS_R_SYSTEM.Name,
SMS_R_SYSTEM.SMSUniqueIdentifier,
SMS_R_SYSTEM.ResourceDomainORWorkgroup,
SMS_R_SYSTEM.Client from SMS_R_System
inner join
SMS_G_System_COMPUTER_SYSTEM
on SMS_G_System_COMPUTER_SYSTEM.ResourceId = SMS_R_System.ResourceId
where
SMS_G_System_COMPUTER_SYSTEM.Manufacturer like "%Microsoft%"
'#
$params = #{
ComputerName = $SccmServerName
Query = $query
NameSpace = "Root\SMS\Site_$SiteCode"
}
Get-CimInstance #params
The query above happens to be for a device collection of Hyper-V guests . You can change $query to whatever you want. Once you get the query working here, you should be able to copy and paste it to/from the SCCM device colleciton UI.
On Powershell Remoting
If the call to Get-CimInstance results in an error that indicates that Powershell remoting or CIM is not working in your case, then you should probably focus on understanding that aspect of your environment. Powershell remoting has some nuanced limitations and prerequisites that depend, among other things, on firewall settings, enabling remoting, and what version of Powershell and operating systems you're running. It's not worth remembering all the details. The reference I use is Chapter 10 of Powershell in Depth, second edition.
If it turns out you must use WMI instead of CIM, it should be fairly straightforward to translate the call to Get-CimInstance to Get-WmiObject.
Frode F. you are the man
yep it works real good.
at the end after the } i put "Remove-PSSession -ComputerName SERVER"
so that when you run it a few times the concurrent session fill up. with this extra line no worries.

Get-WmiObject credentials not working when scheduled

I have a Powershell script to detect disk space on a network server that requires a user/password to access it.
I followed this: http://social.technet.microsoft.com/Forums/en-US/winserverpowershell/thread/440ab7ed-7727-4ff7-a34a-6e69e2dff251/
To get this code:
$password = get-content C:\creds.txt | convertto-securestring
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "username",$password
Get-WmiObject -ErrorAction Stop Win32_LogicalDisk -ComputerName $deviceName -credential $cred -Filter "DeviceID='$darg'"
$deviceName and $darg are already correctly defined.
This works just fine when running the Powershell script manually. But when I set it up as a Windows schedule task, it fails thinking the user/pass is incorrect:
Get-WmiObject : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESS
DENIED))
$disks = Get-WmiObject <<<< -ErrorAction Stop Win32_LogicalDisk -ComputerName $deviceName -credential $cred -Filter
"DeviceID='$darg'"
+ CategoryInfo : NotSpecified: (:) [Get-WmiObject], Unauthorized AccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
Why is this? (The account is a local user on the remote server. I've tried entering the credentials on the Schedule interface but it doesn't let me save it, saying invalid login; maybe due to being a local account) ServerName\LocalUser does not work in the Windows Schedule UI, still gives the incorrect login error.
Here is my comment, re-worded as an answer.
The convertto/from-securestring functions work on a per-user basis (if you don't provide a specific key value). IOW, one user can't read another user's data.
This pre-existing SO question seems relevant. There is also relevant discussion at Powershellcommunity.org.
why dont you set the task to run under the user account and run the wmi request without credential ?

PowerShell code works interactively, but not inside a script

Salutations!
So, I was just on my daily routine of some powershell programming and I got to this little fella:
Get-WmiObject : Invalid class
At line:184 char:19
+ $RECApp = gwmi <<<< Win32_product | ? {$_.Name.Contains($Application)}
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId: GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
The funny thing is that this error is only shown when the code is executed from a script, but when manually entered into the command line the same code works. I don't know if this has something to do with the remote machine which I'm executing the script on, but why should it, if it works when entered manually but not when executed from the script. I'll get you a piece of my code here:
Enter-PSSession -ComputerName $serverName
$App = gwmi Win32_product | ? {$_.Name.Contains($Application)}
$App.Uninstall();
exit
To summarize, why does this code work like a charm when entered manually to the command line, but when executed form a script I get the above error?
Thanks.
Enter-PSSession is for interactive use only. If you put that line in a script, the subsequent lines do not run in a remote session. If you want to run some script remotely, within a script, you would do something like this instead:
$session = New-PSSession -ComputerName $serverName
Invoke-Command -Session $session {
param($name)
$App = gwmi Win32_product | ? {$_.Name.Contains($name)}
$App.Uninstall();
} -arguments $application
There's a bit of a gotcha there as I have to pass $application as an argument to Invoke-Command because that variable does not exist on the remote session. Stepping back a bit, you could write the above a bit simpler like this:
$app = gwmi -computer $servername win32_product | ? {
$_.name.contains($application)
}
Now, the problem with this is that you are pulling all win32_product objects back from the server and filtering on the local machine. This is a lot of unneccessary network traffic. It would be faster if you could filter on the remote machine, so let's modify it a bit more:
$app = gwmi -computer $servername -query `
"select * from win32_product where name = '$application'"
$app.Uninstall()
Now only the win32_product you want will be retrieved from the remote machine. I haven't tested the above gwmi -computer variants, so it's more illustrative of technique and syntax. You may have to play a bit with it.