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
Related
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"
I hope someone can help me with this. We want to see which computers have a HDD and SDD. I have an excel.csv of the computers. I import the computers. But when I export them I never see the csv or its incomplete. Can you tell what part of my script is incorrect. Thank you
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers) {
Write-Host "`nPulling Physical Drive(s) for $computer"
if((Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet)){
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage | Select-Object sort -Property PSComputerName, Model, SerialNumber, MediaType
Export-Csv C:\Temp\devices.csv
}
}
}
Update: 11/11/2021
Thank you everyone for you help
This script worked for me:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\Computers.csv"
{} | Select "ComputerName", "Status", "Model", "SerialNumber", "MediaType" | Export-Csv $ExportTo
$data = Import-csv -path $ExportTo
foreach ($computer in $computers) {
$Online = Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer.computer -Quiet
if ($Online) {
Write-Host $computer.computer " is Online"
$OutputMessage = Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer.computer | Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType
$data.ComputerName = $computer.computer
$data.Status = $OutputMessage.Status
$data.Model = $OutputMessage.Model
$data.SerialNumber = $OutputMessage.SerialNumber
$data.MediaType = $OutputMessage.MediaType
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
} else {
Write-Host $computer.computer " is Offline"
$data.ComputerName = $computer.computer
$data.Status = "Offline"
$data.Model = ""
$data.SerialNumber = ""
$data.MediaType = ""
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Continuing from my comment. . . as is, you would be exporting the results to the remote machine. That's if it was piped properly. You're currently missing a pipe (|) before Export-Csv.
Also, there's no need to invoke the command, as Get-WMIObject has a parameter for remote computers: -ComputerName. It's also a deprecated cmdlet that has been replaced by Get-CimInstance.
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
Write-Host "`nPulling Physical Drive(s) for $computer"
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Side Note: Get-CimInstance accepts an array of strings, meaning you can pass the entirety of $Computers to it. This should allow it to perform the the query in parallel, vs serial (one at a time):
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computers -ErrorAction SilentlyContinue |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
Performing queries one at a time doesn't necessarily mean it's bad. You can actually have more control over the control of flow for your script.
EDIT:
Following up on your comment...you're no longer using your if statement to check if the computer is online before connecting. So given that you keep the if statement, and add an else condition, you can create a calculated property to add another property to export of Status. Then, you can pass it a value of Online, or Offline depending on if the machine is online or not:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Write-Host -Object "`nPulling Physical Drive(s) for $computer"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation -Force
}
else {
Write-Host -Object "`n$Computer is Offline"
[PSCustomObject]#{PSComputerName=$Computer;Status='Offline'} | Export-Csv -Path $ExportTo -Append -Force
}
}
Also:
Always remember that even if you can ping a machine, it doesn't mean you can connect to it.
This can be mitigated by using a CIM Session, or PSSession depending on the type of commands you're running.
To specifically answer the question:
How do I correctly export a CSV file (use Export-Csv)?
You might want to read about PowerShell pipelines and PowerShell cmdlets.
Basically, a cmdlet is a single command that participates in the pipeline semantics of PowerShell. A well written cmdlet is implemented for the Middle of a Pipeline which means that it processes ("streams") each individual item received from the previous cmdlet and passes it immediately to the next cmdlet (similar to how items are processed in an assembly line where you can compare each assembly station as a cmdlet).
To better show this, I have created an easier minimal, complete and verifiable example (MVCE) and replaced your remote command (Invoke-Command ...) which just an fake [pscustomobject]#{ ... } object.
With that;
I have used Get-Content rather then Import-Csv as your example suggest that Computers.csv is actually a text file which list of computers and not a Csv file which would require a (e.g. Name) header and using this property accordingly (like $Computer.Name).
To enforce the pipeline advantage/understanding, I am also using the ForEach-Object cmdlet rather than the foreach statement which is usually considered faster but this is probably not the case here as for the foreach statement it is required to preload all $Computers into memory where a well written pipeline will immediately start processing each item (which in your case happens on a remote computer) while still retrieving the next computer name from the file.
Now, coming back on the question "How do I correctly export a CSV file" which a better understanding of the pipeline, you might place Export-Csv within the foreach loop::
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
} |Export-Csv .\Devices.csv -Append
}
As commented by #lit, this would require the -Append switch which might not be desired as every time you rerun your script this would append the results to the .\Devices.csv file.
Instead you might actually want do this:
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
}
} |Export-Csv .\Devices.csv
Note the differences: the Export-Csv is placed outside the loop and the -Append switch is removed.
Explanation
As with e.g. the ForEach-Object cmdlet, the Export-Csv cmdlet has internally Begin, Process and End blocks.
In the Begin block (which runs when the pipeline is started), the Export-Csv cmdlet prepares the csv file with a header row etc. and overwrites any existing file.
In the Process block (which runs for each item received from the pipeline) it appends each line (data record) to the file.
(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
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.
I'm writing a script to get the Username of any user logged on to any computer. It works fine with 2 Exceptions.
Here is the code:
$computername = Get-Content 'I:\NodeList\SWSL.txt'
Remove-item "I:\NodeList\UsersPC.txt"
foreach ($computer in $computername)
{
if(Test-Connection -Computername $Computer -BufferSize 16 -Count 1 -ea 0)
{
$Uinfo = gwmi win32_computersystem -comp $computer | select Username,Caption #,Manufacturer
$Uinfo | Out-File -Filepath "I:\NodeList\UsersPC.txt" -Append
Write-Host $Uinfo
}else{
Write-Host $computer " Is offline, not responing to ping"
$computer + " Is offline!" | Out-File -Filepath "I:\NodeList\UsersPC.txt" -Append
}
}
First the Output is:
USername Caption
-------- -------
BINYAN\Charlie SYD-WS04
But I would only like to have:
Charlie SYD-WS04
The domain is always the same, so I just need the username and the computer name, and NOT the headings or the "---"
2nd Problem is, we are a render farm and all renders are processed on a user account Called "Render".
For those computers I only get the computer name, Username is Blank.
This user account is a Domain user, but not in an OU like the others (Sydney, Melbourne, Brisbane).
It resides in the default "Users" folder in AD.
You could remove the domain name using a regex that replaces everything until and including a \: '.*?\\'
You get rid of the heading using select -expand or using a foreach loop where you select the properties using $_.PROPERTY
So your statement could look like this:
$Uinfo = gwmi win32_computersystem -comp $computer |
foreach { '{0} {1}' -f ($_.Username -replace '.*?\\'), $_.Caption }
I have no answer for / I dont understand your second question.