I am trying to create a PowerShell script that remotely checks each machine on the domain for a registry key entry, then outputs that key value along with the machine name to a .csv file.
So far the script outputs all the machines on the domain to a .csv file but puts its local registry key value not the value of the remote machine.
Any help would be greatly appreciated, here is what I have so far.
Import-Module ActiveDirectory
$SRVS = Get-ADComputer -Filter * -SearchBase 'DC=mydomain,DC=local' |
select dnshostname
foreach ($SRV in $SRVS) {
$REG = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
$REGKEY = $REG.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat")
$MELT = $REGKEY.GetValue('cadca5fe-87d3-4b96-b7fb-a231484277cc')
"$($SRV);$($MELT)" | Out-File C:\Users\user1\Desktop\regkeys.CSV -Append
}
The statement
$SRVS = Get-ADComputer ... | select dnshostname
gives you a list of custom objects with only one property: dnshostname. But in your loop you're trying to use a property name, which those custom objects don't have. Hence the statement
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
effectively becomes
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $null)
meaning you're opening the local registry, not the registry on a remote host.
Change $SRV.name to $SRV.dnshostname and the problem will disappear.
Once it's been instanced the RegistryKey class does not expose that it's a remote key. That means you have to record the computer name yourself. There's also no standard format for a remote registry value.
If I had a PowerShell v5+, I would use something like this:
Import-Module ActiveDirectory
# No need for the Select-Object here since we're using $SRV.Name later
$SRVS = Get-ADComputer -Filter * -SearchBase 'DC=mydomain,DC=local'
# Create an arraylist to save our records
$Report = New-Object System.Collections.ArrayList
# This try finally is to ensure we can always write out what we've done so far
try {
foreach ($SRV in $SRVS) {
# Test if the remote computer is online
$IsOnline = Test-Connection -ComputerName $SRV.Name -Count 1 -Quiet;
if ($IsOnline) {
# If system is Online
$REG = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
$REGKEY = $REG.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat")
$MELT = $REGKEY.GetValue('cadca5fe-87d3-4b96-b7fb-a231484277cc')
# Create a PSObject record for convenience
$Record = [PSCustomObject]#{
ComputerName = $SRV;
Key = $REGKEY.Name;
Value = $MELT;
}
}
else {
# If system is Offline
# Create a PSObject record for convenience
$Record = [PSCustomObject]#{
ComputerName = $SRV;
Key = '';
Value = '';
}
}
# Add our record to the report
$Report.Add($Record);
}
}
finally {
# Always write out what we have whether or not we hit an error in the middle
$Report | Export-Csv -Path "C:\Users\user1\Desktop\regkeys.csv" -NoTypeInformation
}
That may work on PowerShell v3+, but I don't have it around anymore to test.
Any reason you are trying to printout the actual regkey vs just checking for it's existence?
It either exists or it does not. Say using something like...
Clear-Host
Import-Module ActiveDirectory
$SRVS = (Get-ADComputer -Filter * -SearchBase (Get-ADDomainController).DefaultPartition)
$MeltHive = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat'
$MeltHiveKey = 'cadca5fe-87d3-4b96-b7fb-a231484277cc'
ForEach($Srv in $SRVS)
{
Invoke-Command -ComputerName $Srv.Name -ScriptBlock {
If (Get-ItemProperty -Path $Using:MeltHive -Name $MeltHiveKey -ErrorAction SilentlyContinue)
{"On Host $env:COMPUTERNAME MELT registry information exists"}
Else {Write-Warning -Message "On host $env:COMPUTERNAME MELT registry information does not exist"}
}
}
ForEach($Srv in $SRVS)
{
Invoke-Command -ComputerName $Srv.Name -ScriptBlock {
If ((Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion) -match 'QualityCompat')
{"On Host $env:COMPUTERNAME MELT registry information exists"}
Else {Write-Warning -Message "On host $env:COMPUTERNAME MELT registry information does not exist"}
}
}
Results of both the above is:
WARNING: On host DC01 MELT registry information does not exist
WARNING: On host EX01 MELT registry information does not exist
WARNING: On host SQL01 MELT registry information does not exist
On Host IIS01 MELT registry information exists
Related
I have DHCP script that looks for matching hostnames in all the scopes on the DHCP servers
I first get all the DHCP servers and import a .txt of hostnames
$list = Get-Content C:\script\HostNameList.txt #Defines content it pulls as list
$DHServers = Get-DhcpServerInDC #gives variable name for loop
# Gets all DHCP servers ands scopes
foreach ($Server in $DHServers){
$scopes = Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
I loop through list of hostnames and scopes looking for a match. Somewhere in here is my issue
$Output = foreach ($hostname in $list) { #Calls each item in list a hostname and sends to output
if (test-connection -count 1 -computername $hostname -quiet) #With 1 ping, check if hostname is online
{
foreach ($scope in $scopes){
if($scope | Get-DhcpServerV4Lease -ComputerName $server.dnsname | Where-Object HostName -like "$hostName*" ) #compares the hostname to lease to find which scope it is in
{ $scope.name } #return scope it found hostname in
}
[PSCustomObject]#{ #Rename varibles in data pull for output file
Asset = $hostname
Location = $scope.name #only want the name of the scope
Status = "Online"
}
}
else #statement if hostname is not online
{
Write-host "$hostname Is offline, only Last Location is known. $hostname was added to the output file." -BackgroundColor DarkRed
[PSCustomObject]#{
Asset = $hostname
Location = $scope.name #only want the name of the scope, since the name = Location
Status = "Offline"
}
}
}
$Output #show output in powershell
$Output | Export-Csv -Path C:\script\Asset_Result.csv -NoTypeInformation #outputs .csv
This is what it is doing, the output repeats the last item on the list of DHCP scopes.
Asset Location Status
----- -------- ------
A847 Public Internet Online
A261 Public Internet Offline
A201 Public Internet Online
This is what it should be doing
Asset Location Status
----- -------- ------
A847 FLoor 1 Online
A261 West 1st FL Offline
A201 Floor 3 Online
How can I get $scope.name in my
if($scope | ... statement to go to my PSCustomObject after each iteration?
This:
foreach ($Server in $DHServers){
$scopes = Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
is - in net effect - the equivalent of:
$scopes = Get-DHCPServerv4Scope -ComputerName $DHServers[-1].dnsname #get all scopes
That is, you keep reassigning to the same variable ($scopes) in the loop body, replacing the previous value, so that you end up with only the result from the last loop iteration, for the last server stored in $DHServers, i.e. $DHServers[-1].
The best solution is to rely on PowerShell's ability to use statements such as foreach as an expression whose output - even iterative output from a loop - is automatically collected in an [object[]] array (with two or more outputs) by PowerShell:
# Collect the output from *all* Get-DHCPServerv4Scope calls.
[array] $scopes = foreach ($Server in $DHServers) {
Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
Note: The [array] type constraint (same as: [object[]]) is only necessary if there can situationally be just one output object and you want to ensure that the collected output is always an array.
I'm trying to install/activate a MAK key on remote servers. All of them have RemotePS enabled and firewall exception rules in place.
$Results = Invoke-Command -ComputerName Server1 {
$Props = #{ComputerName = $env:ComputerName}
slmgr.vbs /ipk "12345-12345-12345-12345-12345"
$LicStatus = slmgr.vbs /dlv
$Props.Add('LicenseStatus',$LicStatus)
New-Object -TypeName PSObject -Property $Props
}
$Results | Select-Object ComputerName,LicenseStatus
The above does install the MAK key but I don't get any confirmation of this process which is why I've tried adding in the license check option (/dlv) but get nothing returned in the LicenseStatus field. I'm assuming this is because it returns a multi-value maybe!?
Ultimately I'm just trying to get confirmation that the key was installed. There are articles out there about performing this using RemotePS but they all say a notification message is returned for each computer which isn't the case in my experience: https://4sysops.com/archives/change-a-product-key-remotely-with-powershell/
Any ideas how I can check this?
I would call the slmgr.vbs script using Cscript.exe in order to get the results as string array. Otherwise the system will default to using Wscript.exe which is designed to output everything in a messagebox.
Unfortunately, all output of slmgr is localized, so using a regex or something on the LicenseStatus is a no go (on a Dutch NL machine it reads 'Licentiestatus')
What you can do is using switch /dli, because that returns a string array where the last (not empty) value has the status.
Try
$Results = Invoke-Command -ComputerName Server1 {
# install MAK key
$null = cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /ipk "12345-12345-12345-12345-12345"
# test LicenseStatus
$LicStatus = (((cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /dli) |
Where-Object { $_ -match '\S' })[-1] -split ':', 2)[1].Trim()
# return an object
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
LicenseStatus = $LicStatus
}
}
$Results
Basically as per screen-shot there are multiple worker processes are running on machine in IIS but we need w3wp which is running under Sitecore User Username.
We tried below PS script but getting blank value in User Name column
$processlist = get-process | where {$_.cpu -gt 5 -and $_.Name -eq 'w3wp'} |select Name, #{l="User name";e={$_.getowner().user}} | ft -AutoSize
foreach($proc in $processlist){
if($proc -eq "Sitecore User" ){
C:\Users\Administrator\Documents\someexe.exe $proc.Id "C:\Users\Administrator\Documents\output.dmp"
}
}
and finally we need to perform some action on process Id.
I suggest the following PoSh-Script that should give you all the necessary info and more:
# Ensure to import the WebAdministration module
Import-Module WebAdministration
# Declare the variables
$server = "localhost"
$search = "*"
$wmiQuery=[wmisearcher]"SELECT * FROM __Namespace where NAME like 'WebAdministration' or NAME like 'MicrosoftIISv2'"
$wmiQuery.Scope.Path = "\\" + $server + "\root"
$WebNamespace = $wmiQuery.Get()
# Checking if the the server has IIS installed
if($WebNamespace -like '*WebAdministration*')
{
"IIS found on $server"
$WPlist=Get-WmiObject -NameSpace 'root\WebAdministration' -class 'WorkerProcess' -ComputerName 'LocalHost'
# Loop through the list of active IIS Worker Processes w3wp.exe and fetch the PID, AppPool Name and the startTime
forEach ($WP in $WPlist)
{
if ($WP.apppoolname -like$search)
{
write-host "Found:""PID:"$WP.processid "AppPool_Name:"$WP.apppoolname
(get-process -ID $WP.processid|select starttime)
}
}
}
Else
{
write-host"WARNING: IIS not detected."
}
Ref: https://blogs.msdn.microsoft.com/webtopics/2015/11/28/query-the-active-worker-process-information-in-iis-7-x-using-powershell/
I need to generate a list of all users on our network who are members of their workstation's local administrators group. I found a script here https://gallery.technet.microsoft.com/scriptcenter/List-local-group-members-762b48c5#content which was written to list local group members by executing a WMI query through Powershell. I've tested this script and it works well, but I've been trying to modify it to take in a list of computers to check and that's where I've run into trouble. Here's what I've done:
function LocalAdmins
{
param([string]$GroupName = "Administrators")
begin
{
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Initialize an array to hold the results of the query
$arr = #()
# hash table for storing computer name, member pairings
$hash = #();
}
process
{
foreach ($machine in $WorkStations)
{
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
# Parse out the username from each result and append it to the array
if ($wmi -ne $null)
{
foreach($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
# Return a hash table comprised of two columns: Computer Name & Members
$hash += #{ComputerName=$machine;Members=$arr}
}
}
end
{
return $hash
}
}
When I ran the unmodified script here's what I got as output:
PS > (Get-LocalGroupMembers -ComputerName "<COMPUTER NAME>" -GroupName "Administrators").Members
ACCOUNTNAME
ACCOUNTNAME
ACCOUNTNAME
PS >
However, when I run the version of this script that I modified I get this:
PS > (LocalAdmins -GroupName "Administrators").Members
PS >
I'm fairly certain that the issue lies either in how I've setup the first foreach loop to run the wmi query or how the results of that query are being stored in the hash table. I'm not sure what I could do differently to fix the issue.
Thanks in advance to anyone who can help!
UPDATE
Per mortenya's suggestion, I edited my test text file to only include one computer in it. Doing so, along with taking out the foreach ($machine in $computers) loop worked as expected producing the following result:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- ----
ComputerName {computerName.domain}
Members {account, account, account, account}
>>
However, going back and trying to get this to work when incorporating multiple machines using the code above (I've updated it since my initial post), I get the following:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- -----
ComputerName computerName1.domain
Members {}
ComputerName computerName2.domain
Members {}
>>
Why is it that with one machine in the list I can get the members of the Administrator group, but adding a second computer to the list makes it so I can not retrieve members from that group on either machine?
So, if you're going to use Begin{}, Process{}, and End{}, use them for what they're meant for, in the Begin{} block, initialize all your arrays and constant varaibles.
Begin {
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Store the contents of that list in an array
$computers = #()
$hash = #()
}
Outside of that, I did this same thing a few months ago, it's a little messy, but it spit out a list of computers and who was in the Local Administrators group. It was partially to practice some different methods.
$output = 'c:\psresults\ListOfLocalAdministratorsGroup.txt'
$results = New-Object System.Collections.ArrayList
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupname = ($objgroup.Value).Split("\")[1]
foreach($server in (Get-ADComputer -Filter *).name)
{
$admins = New-Object System.Collections.ArrayList
$group =[ADSI]"WinNT://$server/$objgroupname"
$members = #($group.psbase.Invoke("Members"))
$members | foreach {
$obj = new-object psobject -Property #{
Server = $Server
Admin = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
#$obj
$admins.Add($obj)
}
$results.Add($admins)
}
$results | Out-File $Output
I found the meat of that somewhere and then modified it a bit.
EDIT: I just put this into ISE and it seems to work fine
$machine = "testsrv"
$groupname = "Administrators"
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
if ($wmi -ne $null)
{
foreach ($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
$hash = #{ComputerName=$machine;Members=$arr}
return $hash
Get it working on one machine, then start trying to add the loops back in.
EDIT 2.0:
I made a .txt file with only computer names in it, not the FQDN, that works fine for me. I can run it and get results using your script with minor modification.
Despite what I'd said about the Begin{} block, the $arr variable will need to be initialized inside the foreach ($machine in $WorkStations) loop. The reason for this is that when the loop runs, it will create the $arr array, add the data we want, insert that data into a global variable, and then clean up the $arr variable. If we make this global, it won't be cleaned up until the function is done, and we will just keep adding to it, which isn't what we actually want in this case.
The problem you're having with getting multiple machines to work is likely how you're building your results table.
I've configured winrm on all my desktops via GPO, so I can now use the invoke-command cmdlet to run commands locally on remote machines. When I run net localgroup administrators on my local machine this works and gives me what I want. The problem is I cannot do anything with this data. I cannot pipe out the results to a variable so I can lets say remove specific accounts.
Is there a built in cmdlet that will let me do the same as net localgroup administrators ?
While it's possible to run net localgroup groupname and parse its output, it isn't a very PoSh way of doing this. I'd recommend using the WinNT provider instead:
$computers = 'HostA', 'HostB', 'HostC', ...
$groupname = 'Administrators'
$computers | % {
$group = [ADSI]("WinNT://$_/$groupname,group")
$group.PSBase.Invoke('Members') | % {
$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
}
}
If you want to use Invoke-Command you could do something like this:
$computers = 'HostA', 'HostB', 'HostC', ...
$groupname = 'Administrators'
Invoke-Command -Computer $computers -ScriptBlock {
$group = [ADSI]("WinNT://$env:COMPUTERNAME/$($args[0]),group")
$group.PSBase.Invoke('Members') | % {
$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
}
} -ArgumentList $groupname
You can parse the names from the output.
Here's an example using V4:
(net localgroup administrators).where({$_ -match '-{79}'},'skipuntil') -notmatch '-{79}|The command completed'
Using any version of PowerShell you can simply do the following for whichever machine you want to check:
get-wmiobject -class Win32_Group -computer <computername>(, <computer2> ...)
That will give you the local user accounts. This also gives you ManagementObjects you can use to delete groups if you want, or using PSv3 and newer additional cmdlets are installed on Windows Server (run get-module -list) you can use that may be easier (but still use WMI over WinRM).
I want to build on #Ansgar Wiechers's answer. For my purposes, getting the name of each user was not enough; I had to get the user's domain so that I could see whether the user account was a local account or a domain account.
Instead of calling PSBase.Invoke('Members'), I simply called Members(). To get the path of each user, I casted the object using [ADSI] and then got the Path property. This was the code that I ended up using to get the members of the local Administrators group on the local machine:
([ADSI]"WinNT://localhost/Administrators,group").Members() | % { ([ADSI]$_).Path.Substring(8) }
For multiple computers, it would look like this:
$computers = 'HostA', 'HostB', 'HostC', ...
$computers | % {
([ADSI]"WinNT://$_/Administrators,group").Members() | % {
([ADSI]$_).Path.Substring(8)
}
}
In the end, I extended my script to query all of the computers in my domain, list the local administrators on each computer, and export all of the results to an XML file:
Import-Module ActiveDirectory
Get-ADComputer -Filter { enabled -eq $true } | % {
$result = New-Object PSObject
Add-Member -InputObject $result -MemberType NoteProperty -Name "Name" -Value $_.Name
$local_administrators = ([ADSI]"WinNT://$($_.Name)/Administrators,group").Members() | % { ([ADSI]$_).Path.Substring(8) }
Add-Member -InputObject $result -MemberType NoteProperty -Name "Local Administrators" -Value $local_administrators
$result
} | Export-Clixml "Local Administrators on Machines in the Domain.xml"
You have to install the Remote Server Administration Tools in order to do Import-Module ActiveDirectory. For Windows 7 SP1, you can go to https://www.microsoft.com/en-us/download/details.aspx?id=7887 to download and install the tools and then go to Add or Remove Windows Features to enable them.