Why is my Invoke-Command failing? - powershell

I am putting together a script that will check all of my web servers certificates so that I can monitor when they are set to expire. When the script goes to execute the Invoke-Command I am getting this error:
Here is my code:
Import-Module WebAdministration
$results = #()
$ou = 'OU=test,OU=test,OU=Servers,DC=contoso,DC=com'
$subtree = Get-ADOrganizationalUnit -SearchBase $ou -SearchScope Subtree -filter * | Select-Object -ExpandProperty DistinguishedName
ForEach($dn in $subtree){
$servers = Get-ADComputer -Filter * -SearchBase $dn | select Name
$results += $servers
}#ForEach($dn in $subtree)
$scriptBlock = [scriptblock]::Create({
Import-Module WebAdministration; Get-ChildItem -Path IIS:SSLBindings | ForEach-Object -Process `
{
If($_.Sites){
$certificate = Get-ChildItem -Path CERT:LocalMachine/My |
Where-Object -Property Thumbprint -EQ -Value $_.Thumbprint
[PSCustomObject]#{
Sites = $_.Sites.Value
CertificateDNSNameList = $certificate.DnsNameList
CertificateNotAfter = $certificate.NotAfter
}#[PSCustomObject]
}#If($_.Sites)
}#Import-Module
})#ScriptBlock
ForEach($server in $results){
Invoke-Command -ComputerName $server -ScriptBlock $scriptBlock | Select Sites,CertificateDNSNameList,CertificateNotAfter,PSComputerName #ScriptBlock
}#ForEach($server in $results)
Now, If I take the following line out of the loop and replace $server with an actual server name, I get the results I am looking for:
Invoke-Command -ComputerName ServerName -ScriptBlock $scriptBlock | Select Sites,CertificateDNSNameList,CertificateNotAfter,PSComputerName
Any ideas what I am doing wrong?

Your Results-Array is not an array of string, it is an array of object. Each object have the attribute name. Change your last ForEach-Loop into this and your script will work:
ForEach($server in $results){
Invoke-Command -ComputerName $server.name -ScriptBlock $scriptBlock | Select Sites,CertificateDNSNameList,CertificateNotAfter,PSComputerName #ScriptBlock
}#ForEach($server in $results)

It looks to me like "select Name" is returning an object rather than a string. Try changing the line that fetches the servers to this:
$servers = Get-ADComputer -Filter * -SearchBase $dn | select -ExpandProperty Name

Related

Get Computer description and last logged on user from OU

How do I get a list of computers in a particular OU along with the Description and Last logged on user in a .csv?
$userName = (Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $machine -ErrorAction:SilentlyContinue).UserName
$DisComp = Get-ADComputer -LDAPFilter "(Name=LN-*)" -SearchBase "OU=Computers - Disabled,DC=XXXXX,DC=com" | Select-Object Name
$results = foreach ($Machine in $DisComp) {
$Description = Get-AdComputer -Identity $Machine -Properties * | Select-Object Description
$UserName
$Machine
$Description
}
$results | Export-Csv -Path C:\XXXXX
Define the OU and CSV file paths
$ouPath = "OU=Workstations,DC=contoso,DC=com"
$csvPath = "C:\temp\computer-list.csv"
Use the Get-ADComputer cmdlet to get a list of computers in the OU
$computers = Get-ADComputer -SearchBase $ouPath -Filter * -Properties lastlogondate,description
Loop through each computer and get the description and last logged on user
foreach ($computer in $computers) {
$description = $computer.Description
$lastLoggedOnUser = $computer.LastLogonUser
$data = [PSCustomObject]#{
"Computer Name" = $computer.Name
"Description" = $description
"Last Logged On User" = $lastLoggedOnUser
}
Add the computer data to the CSV file
$data | Export-Csv -Path $csvPath -Append -NoTypeInformation
}
AFAIK there is no AD computer property called LastLogonUser or any other property that holds this information.
To get the user that last logged on, you need to query the windows Eventlog on that computer and search for events with ID 4672
As aside, don't use -Properties * if all you want on top of the default properties returned is the Description property.
Try:
$searchBase = "OU=Computers - Disabled,DC=XXXXX,DC=com"
$Computers = Get-ADComputer -LDAPFilter "(Name=LN-*)" -SearchBase $searchBase -Properties Description
$results = foreach ($machine in $Computers) {
# to get the username who last logged on, you need to query the Security log
$events = Get-WinEvent -ComputerName $machine.Name -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 50 -ErrorAction SilentlyContinue
$lastLogon = if ($events) {
(($events | Where-Object {$_.Properties[1].Value -notmatch 'SYSTEM|NETWORK SERVICE|LOCAL SERVICE'})[0]).Properties[1].Value
}
else {
"Unknown"
}
# output an object
[PsCustomObject]#{
ComputerName = $machine.Name
Description = $machine.Description
LastLoggedOnUser = $lastLogon
CurrentUser = (Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $machine.Name -ErrorAction SilentlyContinue).UserName
}
}
$results | Export-Csv -Path 'C:\Somewhere\Computers.csv' -NoTypeInformation
P.S. You of course need admin permissions to query the eventlog, so perhaps (if you are not a domain admin) you need to use the -Credential parameter on the Get-WinEvent line aswell.

Get Uptime on Each Computer in an Array, Select the machines with the most uptime, and remotely execute a Script on each Machine

The majority of this code was pulled from a blog online, but I think it's exactly the way I need to be tackling this. I want to get the top 4 machines from an OU based on uptime, and run a script that lives on each of the top 4 machines. I know that the problem involves the Array losing access to the Get-ADComputer properties, but I'm unsure of how to pass these new properties back to their original objects. This works as expected until it gets to the foreach loop at the end.
$scriptBlock={
$wmi = Get-WmiObject -Class Win32_OperatingSystem
($wmi.ConvertToDateTime($wmi.LocalDateTime) – $wmi.ConvertToDateTime($wmi.LastBootUpTime)).TotalHours
}
$UpTime = #()
Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree `
| ForEach-Object { $Uptime += `
(New-Object psobject -Property #{
"ComputerName" = $_.DNSHostName
"UpTimeHours" = (Invoke-Command -ComputerName $_.DNSHostName -ScriptBlock $scriptBlock)
}
)
}
$UpTime | Where-Object {$_.UpTimeHours -ne ""} | sort-object -property #{Expression="UpTimeHours";Descending=$true} | `
Select-Object -Property ComputerName,#{Name="UpTimeHours"; Expression = {$_.UpTimeHours.ToString("#.##")}} | Select-Object -First 4 |`
Format-Table -AutoSize -OutVariable $Top4.ToString()
foreach ($Server in $Top4.ComputerName) {
Invoke-Command -ComputerName $Server -ScriptBlock {HOSTNAME.EXE}
}
I'm not married to Invoke-Command in the last foreach but am having the same issues when I try to use psexec. Also, I'm running hostname.exe as a check to make sure I'm looping through the correct machines before I point it at my script.
Here's a streamlined version of your code, which heeds the comments on the question:
# Get all computers of interest.
$computers = Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree
# Get the computers' up-times in hours.
# The result will be [double] instances, but they're also decorated
# with .PSComputerName properties to identify the computer of origin.
$upTimes = Invoke-Command -ComputerName $computers.ConputerName {
((Get-Date) - (Get-CimInstance -Class Win32_OperatingSystem).LastBootUpTime).TotalHours
}
# Get the top 4 computers by up-time.
$top4 = $upTimes | Sort-Object -Descending | Select-Object -First 4
# Invoke a command on all these 4 computers in parallel.
Invoke-Command -ComputerName $top4.PSComputerName -ScriptBlock { HOSTNAME.EXE }

Powershell get Logged on Users OU in Active Directory

I have a text file that contains called MY-OU.txt
OU=offic-Computers,OU=comp-computers,DC=my,DC=company,DC=com
I can get a list of all the computers in that OU:
ForEach ($OU in Get-Content "C:\temp\MY-OU.txt")
{
Get-ADComputer -SearchBase $OU -Filter '*' | Select -Exp Name | Out-File -filepath "C:\temp\MY-OU-computers.txt"
}
And I can get the current logged on users from the output of the code above:
$content = Get-Content C:\temp\MY-OU-computers.txt
$output = foreach ($PC in $content) {Get-WmiObject –ComputerName $PC –Class Win32_ComputerSystem | Select-Object UserName}
$output | Out-File -filepath "C:\temp\CAD-OU-computers-users.txt"
How to combine both parts and get the current logged on users in a specific OU?
Instead of outputting the results from Get-ADComputer to a file, you can assign them to a variable ($Computers), then use that as the input for your next foreach loop:
foreach ($OU in Get-Content "C:\temp\MY-OU.txt"){$Computers += Get-ADComputer -SearchBase $OU -Filter '*' | Select -ExpandProperty Name}
$output = foreach ($PC in $Computers) {Get-WmiObject –ComputerName $PC –Class Win32_ComputerSystem | Select-Object UserName}
$output | Out-File -filepath "C:\temp\CAD-OU-computers-users.txt"

Use WMI to convert a remote SID to username

I am using Invoke-WMIMethod to identify all SIDS beginning with S-1-5-21, like so (thanks to Mathias R. Jessen):
$Keys = Invoke-WmiMethod -Path $ClassPath -Name EnumKey -ArgumentList 2147483651,''
| Select-Object -ExpandProperty sNames | Where-Object {$_ -match 'S-1-5-21-[\d\-]+$'}
I want to convert these SIDs from the remote system to usernames on the remote system using WMI. Is this possible through WMI or Invoke-WmiMethod?
You can use the Win32_SID class to obtain the account name:
foreach($Key in $Keys)
{
$SID = [wmi]"\\$RemoteComputer\root\cimv2:Win32_SID.SID=$Key"
New-Object psobject -Property #{
SID = $Key
Username = $SID.ReferencedDomainName,$SID.AccountName -join '\'
}
}
Rather than grabbing from the registry you could get the same information from the Win32_UserProfile provider. Then if folder name is good enough, consider something like this:
$Computer = "ExampleComputer"
Get-WMIObject Win32_UserProfile -Filter "SID like 'S-1-5-21-*'" -ComputerName $Computer |
select SID,#{name=LocalPath;Expression={Split-Path -leaf $_.LocalPath}}
Otherwise Win32_UserAccount exists but can be really slow with a large domain.
$Computer = "ExampleComputer"
$SIDs = Get-WMIObject Win32_UserProfile -Filter "SID like 'S-1-5-21-*'" -ComputerName $Computer | select -ExpandProperty SID
$UserAccounts = Get-WMIObject Win32_UserAccount -ComputerName $Computer
foreach ($SID in $SIDs) {
foreach ($Account in $UserAccounts) {
If ($SID -eq $Account.SID) {
$Account
}
}
}

Get-Service on multiple remote machines

I can only get the command to return the services on the first computer in the text file.
Is there a better way than for-each for this task?
Get-Service *vault* -ComputerName (Get-Content c:\users\sean\desktop\js.txt) | select name,status,machinename | sort machinename | format-table -autosize
Try it without the get-content. Try this:
Get-Service *vault* -ComputerName c:\users\sean\desktop\js.txt | select name,status,machinename | sort machinename | format-table -autosize
If that doesn't work, then try:
$Computers = Get-Content c:\users\sean\desktop\js.txt
Get-Service *vault* -computername $Computers | Select name,status,machinename |sort machinename |format-table -autosize
If you are eager for a one-liner then try this:
Get-Content c:\users\sean\desktop\js.txt | Get-Service *vault* | Select name,status,machinename |sort machinename |format-table -autosize
I would try the top one first. I would test, but I don't have access to anything I can do a proper test right now.
$Computers = get-content .\desktop\test.txt
$Service = "Vault"
foreach ($computer in $computers) {
$computer
$Servicestatus = get-service -name $Service -ComputerName $computer
}
$Servicestatus | select-object Name,Status,MachineName | format-table -Autosize
This works for me, it gives me each of the computers in the text file, and it looks for the service.
This is what I use. I get the list of computers from an OU in AD.
Import-Module ActiveDirectory
$ou = "OU=Servers,DC=Domain,DC=com"
$servers = Get-ADComputer -Filter * -SearchBase $ou | select-object -expandproperty name
Foreach ($server in $servers){
$Data = Get-Service -ServiceName *IIS*,*TomCat*,*httpd* -ComputerName $server | select machinename,name | sort machinename | format-table -AutoSize
Write($Data) | Out-File .\WebServices.txt -Append
}
$servers = Get-Content .\servers.txt
Foreach ($server in $servers) {
"$server"
Get-Service -ComputerName $Server -name -like "*vault*"
"-------------------"
}
Following a memory limitation limit with older versions of PowerShell, I was required to refresh my code:
Old code:
gwmi win32_service -computer $allcomputers | Select-Object __SERVER,Name,state,startmode,StartName
New code:
`$servers = Get-Content "computers.txt"
Foreach ($server in $servers) {
Get-WmiObject -Class WIN32_service -ComputerName $server |
Select-Object __SERVER,Name,state,startmode,StartName |
Export-Csv -path "Report.CSV" -NoTypeInformation -Append
}`
This is how you can get list of all services in your AD domain:
Get-ADComputer -Filter {OperatingSystem -Like “Windows 10*”} | ForEach-Object {Get-WmiObject -Class Win32_Service -Computer $_.Name}
More useful examples on this (get list of services for all computer listed in a text file, etc.):
https://www.action1.com/kb/list_of_services_on_remote_computer.html
Get-Service -ComputerName ... has a bug in PowerShell 2.0 that only returns the first computer. This is fixed in newer versions so if you upgrade to PowerShell 3.0 or newer, your original code will work fine.
As a workaround, use a foreach-loop to run Get-Service once for each computer:
Get-Content c:\users\sean\desktop\js.txt |
ForEach-Object { Get-Service -Name *vault* -ComputerName $_ } |
Select-Object -Property Name, Status, MachineName |
Sort-Object -Property MachineName |
Format-Table -AutoSize
Nick's solution totally doesn't work for me. I ended up writing a quick and dirty one that works:
$servers = Get-Content .\servers.txt
Foreach ($server in $servers) {
"$server"
Get-Service *vault*
"-------------------"
}