get-content 'C:\assets.txt' | % {
$computer = $_
. 'c:\PSTools\PsLoggedon.exe' -accepteula -l -x \\$Computer 3>$null |
? {$_ -match '^\s{2,}((?<domain>\w+)\\(?<user>\S+))'} |
Select-Object `
#{n='Computer';e={$Computer}},
#{n='Domain';e={$matches.Domain}},
#{n='User';e={$Matches.User}} |
? user -notmatch '^Connecting$|^Users$|^NT$'
}
This is what I am using to get all of the currently logged on computers. Is there a way I can combine this with Get-ADUser so I ca pull straight from AD rather than from a txt document?
• Sorry, but currently there is no way through which you can integrate this ‘Psloggedon.exe’ utility with Active directory commands, i.e., ‘Get-AdUser’. But you can retrieve the details of currently logged on users on different computers in the network remotely by executing the below powershell function: -
‘ function Get-LoggedOnUser
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($comp in $ComputerName)
{
$output = #{ 'ComputerName' = $comp }
$output.UserName = (Get-WmiObject -Class win32_computersystem -ComputerName $comp).UserName
[PSCustomObject]$output
}
} ‘
The above script will give you currently logged on users on several computer systems in the network that you pass on in place of ‘COMPUTERNAME’ as below. Please note that you must give a list of computers separated by commas when using the above script for multiple computer systems.
If you have AD in your environment, then you can check the Domain Controller logs to see when an Active Directory user account logs on and it will also tell the machine that the user is logged onto. Refer the below links for more on this: -
http://technet.microsoft.com/en-us/library/cc787176(v=ws.10).aspx
http://technet.microsoft.com/en-us/library/bb742435.aspx
http://www.windowsecurity.com/articles/deciphering-authentication-events-domain-controllers.html
Also, find the below link for more information and reference on the above: -
https://4sysops.com/archives/how-to-find-a-logged-in-user-remotely-using-powershell/
Powershell script to see currently logged in users (domain and machine) + status (active, idle, away)
You would use PowerShell's Get-ADComputer to do this job, not Get-ADUser. Here's a script which does all this work for you. The below mainly lifted from the public domain here and only slightly modified. It pulls and pipes all AD domain computers into C:\Computers.txt, then PS-remotes into each computer in that list to find the logged in, interactive user, and their last login date. Gives you a report file named C:\LoggedOnResults.txt in a nice tabled format.
# Finds and pipes all AD domain computers into Computers.txt, then PS-remotes into each computer in the list to find the logged in, interactive user, and their last login date. Generates a report file named C:\LoggedOnResults.txt, in a nice tabled format.
# Deletes the current file C:\Computers.txt (if it exists)
$FileName = "C:\Computers.txt"
if (Test-Path $FileName) {
Remove-Item $FileName
write-host "$FileName has been deleted"
}
else {
Write-host "$FileName doesn't exist"
}
# 0. Capture all AD computers into a text file named Computers.txt
# importing dependancy, assuming it's already installed.
# Install RSAT for Windows workstation, AD DS role for Windows Server if missing
Import-Module "ActiveDirectory"
Get-ADComputer -Filter {(OperatingSystem -like "*windows*") -and (Enabled -eq "True")} | Select -Expand Name | Out-File "C:\Computers.txt"
# 1. Create scriptblock to target computer will execute
$SB = {
$explorerprocesses = #(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
if ($explorerprocesses.Count -eq 0) {
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME;
Username = [string]::Empty
LoggedOnSince = [string]::Empty
}
} else {
foreach ($i in $explorerprocesses) {
$Username = $i.GetOwner().User
$Domain = $i.GetOwner().Domain
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME ;
Username = '{0}\{1}' -f $Domain,$Username ;
LoggedOnSince = ($i.ConvertToDateTime($i.CreationDate)) ;
}
}
}
} # endof scriptblock
# 2. Create an empty array to store results
$results = #()
# 3. Query target computers using PSRemoting
Get-content "C:\Computers.txt" | ForEach-Object -Process {
$computer = $_
try {
$results += Invoke-Command -ComputerName $Computer -ScriptBlock $SB -ErrorAction Stop
} catch {
Write-Warning -Message "Faild to use PSremoting on $Computer because $($_.Exception.Message)"
}
}
# 4. Display the results
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize
# 5. Send results to a text file
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize | Out-File -FilePath "C:\LoggedOnResults.txt"
Related
I am trying to come up with a PowerShell script that will give me an accurate count of the number of user profiles that are on a pc. Excluding any pc that has less that 5 accounts. I don't use PowerShell that often so I don't know the correct syntax for it.
$excludedAccounts = #('admin', "Administrator", "Domain Admins", "LocalAdmin")
$userProfiles = Get-ChildItem -Path "C:\Users" -Directory | Select-Object -ExpandProperty Name
$count = 5
($profile in $userProfiles) {
(-not ($excludedAccounts -contains $profile)) {
$count++
}
}
Write-Host "Number of user profiles (excluding domain and local admin accounts): $count"
This works pretty well, but it isn't what I need.
In your code you forgot a foreach and an if, but actually you can do this with a simple Where-Object clause in which you do a regex -notmatch on string admin like this:
# get all user profile folder names except for those that have the word 'admin' in it
$userProfiles = #(Get-ChildItem -Path "C:\Users" -Directory | Where-Object { $_.Name -notmatch 'admin' }).Name
Write-Host "Number of user profiles (excluding domain and local admin accounts): $($userProfiles.Count)"
If you need to do this on multiple computers, use Invoke-Command like so:
$cred = Get-Credential -Message 'Please enter your admin credentials here'
$computers = 'pc01', 'pc02', 'pc03' # the list of computers to probe
$result = Invoke-Command -ComputerName $computers -Credential $cred -ScriptBlock {
# get all user profile folder names except for those that have the word 'admin' in it
$userProfiles = #(Get-ChildItem -Path "C:\Users" -Directory | Where-Object { $_.Name -notmatch 'admin' }).Name
# now output an object to collect in the $result variable
[PsCustomObject]#{
'ComputerName' = $env:COMPUTERNAME
'Non-Admin Profiles' = $userProfiles.Count
}
}
Then if you want to see what computers have 5 or more non admin profiles for instance you do
$result | Where-Object { $_.'Non-Admin Profiles' -ge 5 }
Beginner question. We only grant access to servers by AD group. We need to report who has admin access to a list of Windows servers. My auditor likes my Server Admins script however she also wants to know the group members first, last name. I don't need to use the ADGroupMember script, if there is a better way.
If someone could point me in the right direction that will be great. It's important I understand so I can do it myself next time : )
Thanks in advance
$computers = Get-content "c:\scripts\servers.txt"
ForEach ($Line In $computers)
{
#write-host $Line
Invoke-command -ComputerName $line -ScriptBlock { net localgroup administrators} | Get-ADGroupMember -Identity "$_????what goes here????" |%{get-aduser $_.SamAccountName | select userPrincipalName } | out-file "c:\scripts\'$line'LocalAdmin.txt"
}
This script works great but does not list out group members first, lastname
$computers = Get-content "c:\scripts\servers.txt"
ForEach ($Line In $computers)
{
#write-host $Line
Invoke-command -ComputerName $line -ScriptBlock { net localgroup administrators} | out-file "c:\scripts\'$line'LocalAdmin.txt"
}
If you really need information about the users in the local Administrators group, you can use the cmdlets from the PSv5.1+ Microsoft.PowerShell.LocalAccounts module.
However, note that local accounts just have a single .FullName property, not separate first and last name ones. Also, this property may or may not be filled in:
Invoke-Command -ComputerName (Get-Content c:\scripts\servers.txt) -ScriptBlock {
Get-LocalGroupMember -Group Administrators |
Where-Object ObjectClass -eq User |
Select-Object Sid |
Get-LocalUser
} |
Sort-Object PSComputerName |
Select-Object PSComputerName, Name, FullName
If domain users are among the group's members and you do need separate first and last name information, pipe to Get-ADUser instead of to Get-LocalUser - you can distinguish users by their source (where they are defined) via the .PrincipalSource property, available on the output objects from Get-LocalGroupMember from Window 10 / Windows Server 2016.
An alternative to mklement0's helpful answer, somewhat old school, using [adsi]:
$servers = Get-Content c:\scripts\servers.txt
Invoke-Command -ComputerName $servers -ScriptBlock {
$adsi = [adsi]"WinNT://$env:COMPUTERNAME,computer"
$adsi.PSBase.Children.Find('Administrators').PSBase.Invoke('members') |
ForEach-Object {
$Name = $_.GetType().InvokeMember('Name','GetProperty',$null,$_,$null)
$class = $_.GetType().InvokeMember('Class','GetProperty',$null,$_,$null)
$adspath = $_.GetType().InvokeMember('ADSPath','GetProperty',$null,$_,$null)
$sid = [System.Security.Principal.SecurityIdentifier]::new(
$_.GetType().InvokeMember('objectsid','GetProperty',$null,$_,$null),0
).Value
[pscustomobject]#{
Name = $Name
Class = $Class
Path = $adspath -replace '^WinNT://'
SecurityIdentifier = $sid
}
} | Sort-Object Class -Descending
} | Where-Object Class -EQ User
(I am a newbie to PS who has mostly used VBS and Batch so I am still working on following PS scripts)
I need to delete most (but not all) domain accounts off of all 500 of our systems.
Some of these are from a specific list.
Some follow a generic format *21, *19, etc...
I can find scripts that will let me delete a specific user account but I can't figure out how to pass it the long list or use the wildcards...
This one seems promising if I can figure out how to get the values need in it...
:: This Script taken from https://www.nextofwindows.com/delete-user-profiles-on-a-remote-computer-in-powershell
$Computer = Read-Host "Please Enter Computer Name: "
$user = Read-Host "Enter User ID: "
Invoke-Command -ComputerName $computer -ScriptBlock {
param($user)
$localpath = 'c:\users\' + $user
Get-WmiObject -Class Win32_UserProfile | Where-Object {$_.LocalPath -eq $localpath} |
Remove-WmiObject
} -ArgumentList $user
It sounds like you are most of the way there. Just use some pipelines and a foreach loop or two.
This will attempt to remove all users in the list from all computers in the list:
# define function that takes user list from pipeline
function Remove-User {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true,Mandatory=$true)]
[string]
$user,
[Parameter(Mandatory=$true)]
[string]
$computer
)
process {
# copied from question
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class Win32_UserProfile |
Where-Object { $_.LocalPath -eq "c:\users\${$user}" } |
Remove-WmiObject
} -ArgumentList $user
}
}
# get your lists whatever way makes sense
$userList = Import-Csv -Path "users.csv" -Delimiter ','
$computerList = Import-Csv -Path "computers.csv" -Delimiter ','
# call function to remove all users for each computer
$computerList | ForEach-Object {
$userList | Remove-User -computer $_
}
I'm not sure where you get your list from, but I used a csv just because.
*note: This is going off the assumption that the Invoke-Command portion of code from nextOfWindows does what it says
I want to automate our auditing process, where we provide the local admin members, currently this is done with screenshots. I cobbled together the below code, which prompts for a server name and create a file with the local admin members. However this requires me to rename the file.
I want to be able to input the server name and have that also be the out-file name. I'm just not seeing the tree through the forest an how I go about it. Lots of stuff for appending file names but I didn't see anything for renaming a file you create.
Thanks
function get-localadmins {
[cmdletbinding()]
Param(
[string]$computerName
)
$group = get-wmiobject win32_group -ComputerName $computerName -Filter "LocalAccount=True AND SID='S-1-5-32-544'"
$query = "GroupComponent = `"Win32_Group.Domain='$($group.domain)'`,Name='$($group.name)'`""
$list = Get-WmiObject win32_groupuser -ComputerName $computerName -Filter $query
$list | % {$_.PartComponent} | % {$_.substring($_.lastindexof("Domain=") + 7).replace("`",Name=`"", "\")}
}
$Workstation = Read-Host "Computer Name"
get-localadmins $Workstation | Out-File c:\temp\ENTERSERVERNAME_LocalAdmin.txt
Try this out
| Out-File -FilePath "C:\Temp\${Workstation}_LocalAdmin.txt" -Append
Complete powershell and scripting noob here - I don't even know enough to be dangerous. I need to query all PCs in the domain to determine what their local Administrators group membership is and send that output to a text/csv file.
I've tried numerous things like:
Import-Module -Name ActiveDirectory
Get-ADComputer -filter * |
Foreach-Object {
invoke-command {net localgroup administrators} -EA silentlyContinue |
} |
Out-File c:\users\ftcadmin\test.txt
Which gets me an identical list repeated but seems to be hitting every domain PC. I'm guessing it's not actually running on the remote PCs though. Also tried:
$computers = Get-Content -Path c:\users\ftcadmin\computers.txt
invoke-command {net localgroup administrators} -cn $computers -EA silentlyContinue
Get-Process | Out-File c:\users\ftcadmin\test.txt
which is limited by predetermined list of PCs in the computers.txt file. A third thing I tried was this:
$a = Get-Content "C:\users\ftcadmin\computers.txt"
Start-Transcript -path C:\users\ftcadmin\output.txt -append
foreach ($i in $a)
{ $i + "`n" + "===="; net localgroup "Administrators"}
Stop-Transcript
which seems to have the most potential except the output is just
computername1
====
computername2
====
etc without any listing of the group members.
Any ideas from the community?
Copy and paste this function into your PowerShell console.
function Get-LocalGroupMember
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$ComputerName = 'localhost',
[Parameter()]
[ValidateNotNullOrEmpty()]
[pscredential]$Credential
)
process
{
try
{
$params = #{
'ComputerName' = $ComputerName
}
if ($PSBoundParameters.ContainsKey('Credential'))
{
$params.Credential = $Credential
}
$sb = {
$group = [ADSI]"WinNT://./$using:Name"
#($group.Invoke("Members")) | foreach {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
}
Invoke-Command #params -ScriptBlock $sb
}
catch
{
Write-Error $_.Exception.Message
}
}
}
Then, try this to use it:
Get-ADComputer -filter * | foreach {Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name }
If you'd like to do some formatting you could get a list of computers and have all of the members beside each one.
Get-ADComputer -filter * | foreach {
$members = Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name
[pscustomobject]#{
'ComputerName' = $_.Name
'Members' = $members
}
}
This requires at least PowerShell v3. If you don't have that, I highly recommend upgrading to PowerShell v4 anyway.
In powershell version 5.1, the version that comes with WMF 5.1 or Windows 10 version 1607, there are now (finally) cmdlets for managing local users and groups. https://technet.microsoft.com/en-us/library/mt651681.aspx
For example, to get the members of the local Administrators group, you could use
Get-LocalGroupMember -Group "Administrators"
Unfortunately, these new cmdlets don't have a ComputerName parameter for running the command remotely. You'll need to use something like Invoke-Command to run the command remotely and the remote computer will need to have powershell version 5.1 as well.
The ComputerName parameter of Invoke-Command cmdlet doesn't accept pipeline input and it only accepts strings, so we first need to expand the name property returned by Get-ADComputer and store the strings in a variable. Thankfully the parameter accepts multiple strings, so we can simply use the $names variable in a single invoke-command call. Example below:
$names = Get-ADComputer -Filter * | Select-Object -ExpandProperty name
Invoke-Command -ScriptBlock {Get-LocalGroupMember -Group "Administrators"} -ComputerName $names
This is actually something that I have recently worked on a fair bit. It seems that there are two conventional ways to get members from the local Administrators group: WMI and ADSI.
In my opinion better method is to use a WMI query to get the members as this includes domain, so you know if the user/group listed is local to the server or is a domain account.
The simpler way using ADSI does not include this information, but is less likely to get Access Denied types of errors.
Towards this end I have cobbled together a script that checks AD for machines that have been active in the last 30 days (if it's not online, there's no need to waste time on it). Then for each of those it tries to do a WMI query to get the admin members, and if that fails it resorts to an ADSI query. The data is stored as a hashtable since that's a simple way to manage the nested arrays in my opinion.
$TMinus30 = [datetime]::Now.AddDays(-30).ToFileTime()
$Domains = 'ChinchillaFarm.COM','TacoTruck.Net'
$ServerHT = #{}
$Servers = ForEach($Domain in $Domains){Get-ADObject -LDAPFilter "(&(objectCategory=computer)(name=*))" -Server $Domain | ?{$_.lastLogonTimestamp -gt $TMinus30}}
$Servers.DNSHostName | %{$ServerHT.Add($_,$Null)}
ForEach($Server in ($Servers | ?{$(Test-Connection $_.DNSHostName -Count 1 -Quiet)})){
Try{
$GMembers = Get-WmiObject -ComputerName $Server -Query "SELECT * FROM win32_GroupUser WHERE GroupComponent = ""Win32_Group.Domain='$Server',Name='Administrators'"""
$Members = $GMembers | ?{$_.PartComponent -match 'Domain="(.+?)",Name="(.+?)"'}|%{[PSCustomObject]#{'Domain'=$Matches[1];'Account'=$Matches[2]}}
}
Catch{
$group = [ADSI]("WinNT://$Server/Administrators,group")
$GMembers = $group.psbase.invoke("Members")
$Members = $GMembers | ForEach-Object {[PSCustomObject]#{'Domain'='';'Account'=$_.GetType().InvokeMember("Name",'GetProperty', $null, $_, $null)}}
}
$ServerHT.$Server = $Members
}
Then you just have to output to a file if desired. Here's what I would do that should output something like what you want:
$ServerHT.keys|%{"`n"+("="*$_.length);$_;("="*$_.length)+"`n";$ServerHT.$_|%{"{0}{1}" -f $(If($_.Domain){$_.Domain+"\"}), $_.Account}}
This would give you something like the following if the first two servers responded to WMI queries and the third did not:
===========
ServerSQL01
===========
ServerSQL01\SQLAdmin
TacoTruck\TMTech
TacoTruck\Stan2112
======
XWeb03
======
ChinchillaFarm\Stan2112
============
BrokenOld486
============
TMTech
Guest
That last one would trigger some red flags in my book if somebody put the Guest account in the local admin group, but I suppose that's probably one of the reason that you're doing this in the first place.