I'm in the early stages of learning powershell, and I'm trying to put together a script that remotely gathers information from our IIS servers, but I'm encountering several issues.
The first one is that the IP Address and OU columns remain empty in the output file.
The second one is that I'm not able to format the Administrator group column to have 1 group per line, or delimited by commas.
This is the current version of the code:
$computers = Get-Content "C:\servers.txt"
#Running the invoke-command on remote machine to run the iisreset
$output = foreach ($computer in $computers)
{
Write-Host "Details from server $computer..."
try{
Invoke-command -ComputerName $computer -ErrorAction Stop -ScriptBlock{
# Ensure to import the WebAdministration and AD module
Import-Module WebAdministration
Import-Module ActiveDirectory
$webapps = Get-WebApplication
$list = #()
foreach ($webapp in get-childitem IIS:\AppPools\)
{
$name = "IIS:\AppPools\" + $webapp.name
$item = #{}
$item.server = $env:computername
$item.WebAppName = $webapp.name
$item.Version = (Get-ItemProperty $name managedRuntimeVersion).Value
$item.State = (Get-WebAppPoolState -Name $webapp.name).Value
$item.UserIdentityType = $webapp.processModel.identityType
$item.Username = $webapp.processModel.userName
$item.Password = $webapp.processModel.password
$item.OU = (Get-ADComputer $_ | select DistinguishedNAme)
$item.IPAddress = (Get-NetIPAddress -AddressFamily IPv4)
$item.Administrators = (Get-LocalGroupMember -Group "Administrators")
$obj = New-Object PSObject -Property $item
$list += $obj
}
$list | select -Property "Server","WebAppName", "Version", "State", "UserIdentityType", "Username", "Password", "OU", "Ip Address", "Administrators"
}
} catch {
Add-Content .\failedservers.txt -Force -Value $computer
}
}
$output | Export-Csv -Path .\output.csv
#Stop-Transcript
I'd really appreciate any input on how to get it to work properly or improve on the code itself.
Thanks in advance!
For the OU property, you'll want to reference $env:COMPUTERNAME instead of $_:
$item.OU = (Get-ADComputer $env:COMPUTERNAME |Select -Expand DistinguishedName)
For the IPAddress and Administrators fields you'll want to use -join to create comma-separated lists of the relevant values:
$item.IPAddress = (Get-NetIPAddress -AddressFamily IPv4).IPAddress -join ','
$item.Administrators = (Get-LocalGroupMember -Group "Administrators").Name -join ','
Related
What I am trying to achieve here is add the servers and the updates that are not installed on the server to an array and create a new object that is going to display the names of the servers in one column and the missing updates on another column, but at the end I am getting an empty Grid-View table.
The values for the servers and updates are read from a file.
Write-Host
#Read the password from stdin and store it in a variable
$password = Read-Host -AsSecureString -Prompt "Enter your password"
Write-Host
#Get credentials and password for later user
$cred = New-Object System.Management.Automation.PSCredential ("Administrator#testing.local", $password )
#Get the list of available servers to test
$servers = Get-Content -Path $HOME\Desktop\servers.txt
#Get the list of available updates that need to be installed on the server
$available_updates = Get-Content $HOME\Desktop\update.txt
$add_updates = #()
$add_updates_and_servers = #()
#Get each server name from the list and execute the following commands
foreach ($server in $servers) {
#Test if the server is reponding
$ping = Test-Connection $server -Count 1 -Quiet
#If the above command returns True continue
if ($ping -eq "True") {
#Write a message saying Testing server_name
Write-Host "Testing $server"
foreach ($update in $available_updates) {
#Check if update is installed
$updates_from_os = Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock { Get-HotFix | Select-Object -Property HotFixID | Where-Object -Property HotFixID -EQ $Using:update } -HideComputerName | Select-Object -ExpandProperty HotFixID
if (!$updates_from_os) {
$add_updates += $update
}
}
New-Object -TypeName PSObject -Property $updates -OutVariable final
$updates = #{
"Server" = $server
"Updates" = $add_updates
}
}
$add_updates_and_servers += $final
}
$add_updates_and_servers | Out-GridView
For what is probably happening with your script:
I suspect that each time you calling the statement New-Object -TypeName PSObject -Property $updates -OutVariable final You overwriting any previous created $final object which references to the same objects as your $add_updates_and_servers collection.
Anyways, try to avoid using the increase assignment operator (+=) to create a collection, instead stream the results to a variable (or even better, directly to next/final cmdlet: ... }| Out-GridView).
Something like:
$add_updates_and_servers = foreach ($server in $servers) {
$ping = Test-Connection $server -Count 1 -Quiet
if ($ping -eq "True") {
Write-Host "Testing $server"
$add_updates = #(
foreach ($update in $available_updates) {
$updates_from_os = Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock { Get-HotFix | Select-Object -Property HotFixID | Where-Object -Property HotFixID -EQ $Using:update } -HideComputerName | Select-Object -ExpandProperty HotFixID
if (!$updates_from_os) { $update }
}
)
[PSCustomObject]#{
"Server" = $server
"Updates" = $add_updates
}
}
}
Note: in case you want each $update in a separate column, also have a look at: Not all properties displayed
I'm trying to create a script that can get the user profiles that haven't logged on a specific computer within 30 days NOT using active directory but my script didn't work. I am using Powershell version 3. This is my code:
netsh advfirewall firewall set rule group="Windows Management Instrumentation (WMI)" new enable=yes
$ComputerList = Get-Content C:\temp\Computers1.txt
$myDomain = Get-Content C:\temp\Domain.txt
$csvFile = 'C:\temp\Profiles.csv'
# Create new .csv output file
New-Item $csvFile -type file -force
# Output the field header-line to the CSV file
"HOST,PROFILE" | Add-Content $csvFile
# Loop over the list of computers from the input file
foreach ($Computer in $ComputerList) {
# see if ping test succeeds for this computer
if (Test-Connection $Computer -Count 3 -ErrorAction SilentlyContinue) {
$ComputerFQDN = $Computer + $myDomain
$Profiles = Get-WmiObject -Class Win32_UserProfile -Computer $ComputerFQDN | Where{$_.LocalPath -notlike "*$env:SystemRoot*"}
foreach ($profile in $profiles) {
try {
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.LocalPath) | Where {((Get-Date)-$_.lastwritetime).days -ge 30}
#| Where-Object {$_.LastLogonDate -le $CurrentDate.AddDays(-60)}
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
} catch {
$objusername = $profile.LocalPath
}
switch($profile.status){
1 { $profileType="Temporary" }
2 { $profileType="Roaming" }
4 { $profileType="Mandatory" }
8 { $profileType="Corrupted" }
default { $profileType = "LOCAL" }
}
$User = $objUser.Value
#output profile detail for this host
"$($Computer.toUpper()), $($objusername)" | Add-Content $csvFile
}
} else {
#output failure message for this host
"$($Computer.toUpper()), PING TEST FAILED" | Add-Content $csvFile
}
#LOOP
}
I tried to change the -ge to -le in the line $objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.LocalPath) | Where {((Get-Date)-$_.lastwritetime).days -ge 30}, as well as changing the range after it but it still gave me the same list of computers regardless of my changes.
There are a few problems with the script, most notable is that your use of Where-Object is testing an object (SID) that doesn't know anything about dates.
I would break it down a little differently. I would write a function to catch all the stuff I need to do to attempt to figure out the last logon. That's my goes in my stack of utility functions in case I need it again.
Then I have something to use that function which deals with implementing the logic for the immediate requirement.
So you end up with this. It's a bit long, see what you think.
function Get-LastLogon {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true)]
[String]$ComputerName = $env:COMPUTERNAME
)
process {
Get-WmiObject Win32_UserProfile -ComputerName $ComputerName -Filter "Special='FALSE'" | ForEach-Object {
# Attempt to get the UserAccount using WMI
$userAccount = Get-WmiObject Win32_UserAccount -Filter "SID='$($_.SID)'" -ComputerName $ComputerName
# To satisfy WMI all single \ in a path must be escaped.
# Prefer to use NTUser.dat for last modification
$path = (Join-Path $_.LocalPath 'ntuser.dat') -replace '\\', '\\'
$cimObject = Get-WmiObject CIM_DataFile -Filter "Name='$path'" -ComputerName $ComputerName
if ($null -eq $cimObject) {
# Fall back to the directory
$path = $_.LocalPath -replace '\\', '\\'
$cimObject = Get-WmiObject CIM_Directory -Filter "Name='$path'" -ComputerName $ComputerName
}
$lastModified = $null
if ($null -ne $cimObject) {
$lastModified = [System.Management.ManagementDateTimeConverter]::ToDateTime($cimObject.LastModified)
}
# See if LastUseTime is more useful.
$lastUsed = $null
if ($null -ne $_.LastUseTime) {
$lastUsed = [System.Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime)
}
# Profile type
$profileType = switch ($_.Status) {
1 { "Temporary" }
2 { "Roaming" }
4 { "Mandatory" }
8 { "Corrupted" }
0 { "LOCAL" }
}
[PSCustomObject]#{
ComputerName = $ComputerName
Username = $userAccount.Caption
LastChanged = $lastModified
LastUsed = $lastUsed
SID = $_.SID
Path = $_.LocalPath
ProfileType = $profileType
}
}
}
}
$myDomain = Get-Content C:\temp\Domain.txt
Get-Content C:\temp\Computers1.txt | ForEach-Object {
$ComputerName = $_ + $myDomain
if (Test-Connection $ComputerName -Quiet -Count 3) {
Get-LastLogon -ComputerName $ComputerName | Select-Object *, #{Name='Status';Expression={ 'OK' }} |
Where-Object { $_.LastChanged -lt (Get-Date).AddDays(-30) }
} else {
# Normalise the output so we don't lose columns in the export
$ComputerName | Select-Object #{Name='ComputerName';e={ $ComputerName }},
Username, LastChanged, LastUsed, SID, Path, ProfileType, #{Name='Status';Expression={ 'PING FAILED' }}
}
} | Export-Csv 'C:\temp\Profiles.csv' -NoTypeInformation
I was working tonight to re-write an existing server health check script to store its values in a hashtable, and that part is working fine. However, I want the results to go to a CSV file, and that file only to be populated with servers where I've tagged them as requiring action. Currently those are generating event ID 7011, or failing a ping test by Test-Connection.
Here's the code:
$CheckServer = #{}
$Servers = (Get-Content $Dir\Test.txt)
foreach ($Server in $Servers) {
$CheckServer.EventID7011 = Get-Eventlog -LogName System -ComputerName $Server -Newest 1 |
Where-Object {$_.EventId -eq 7011} | select Message
if ($CheckServer.EventID -ne $Null) {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq = "Yes"
}
$CheckServer.Ping = Test-Connection -ComputerName $Server -Count 1 -Quiet
if (! $CheckServer.Ping) {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq ="Yes"
$CheckServer.Ping = "Offline"
} else {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq = "No"
$CheckServer.Ping = "Online"
}
New-Object -TypeName PSObject -Property $CheckServer |
Export-Csv "ScanResults.csv" -NoTypeInformation -Append
}
I need the correct code at the end, as it stands, the script works fine for collecting/storing the data in the hashtable array $CheckServer, but I'd like to only select those servers that require action. So, if I'm scanning 100 servers, and 2 of them are in a ping fail state, I want only those selected and sent to Export-Csv.
If you want only servers that don't respond to Test-Connection in the output anyway it would be much simpler to just use a Where-Object filter on the server list:
Get-Content "$Dir\Test.txt" |
Where-Object { -not (Test-Connection -Computer $_ -Count 1 -Quiet) } |
Select-Object #{n='Server';e={$_}}, #{n='ActionReq';e={'Yes'}},
#{n='Ping';e={'Offline'}} |
Export-Csv 'ScanResults.csv' -NoType -Append
You need to store the objects into a list before you can filter and export them. See the lines with comments in your code:
$CheckServer = #{}
$serverObjects = #() # create a list of server objects
$Servers = (get-content $Dir\Test.txt)
ForEach ($Server in $Servers) {
$CheckServer.EventID7011 = get-eventlog -LogName System -ComputerName
$Server -newest 1 | where-object {$_.eventID -eq 7011} |select message
If ($CheckServer.EventID -ne $Null) {
$CheckServer.Server="$Server"
$CheckServer.ActionReq = "Yes"}
$CheckServer.Ping = Test-Connection -ComputerName $Server -count 1 -quiet
if (! $CheckServer.Ping) {
$CheckServer.Server="$Server"
$CheckServer.ActionReq ="Yes"
$CheckServer.Ping= "Offline"}
Else {
$CheckServer.Server="$Server"
$CheckServer.ActionReq ="No"
$CheckServer.Ping= "Online"}
# Add the server object to the list
$serverObjects += New-Object -TypeName PSObject -Property $CheckServer
}
}
# now filter it:
$serverObjects | where ActionReq -eq "Yes" | Export-Csv -Path "...."
The code below turns off firewall on each remote computers and return any computers that was turned off. I am also trying to retrieve software that has been authorized to pass through firewall for each computer.
I understand that I am using try, catch so is there any way to print the output of $Appfilter to offComp&programsALLO.txt ? The text file just prints the value of $Appfilter.
The output should ideally look like:
Computers:
"name of computer" followed by "programs allowed"
Here is the code:
Get-ADComputer -Filter * | Select-Object -ExpandProperty Name | Out-File .\ADcomputers.txt
$LaunchLine = 'powershell.exe -Version 4.0 -Command "& {netsh advfirewall set allprofiles state off}"'
$Appfilter = 'powershell.exe -Version 4.0 -Command "& {Get-NetFirewallApplicationFilter -program * | fl program}"'
$ComputerList = Get-Content .\adcomputers.txt
foreach($Computer in $ComputerList) {
[String]$wmiPath = "\\{0}\root\cimv2:win32_process" -f $computer
try {
[wmiclass]$Executor = $wmiPath
$executor.Create($LaunchLine, $Appfilter)
} catch {
Add-Content offComp&programsALLO.txt "computers:$Computer, $Appfilter "
}
}
I would use Invoke-Command with the -ComputerName parameter if possible:
#store AD Computer names in an array
$computerList = (Get-ADComputer -Filter *).Name
#declare results arrays
$results = #()
$offline = #()
#for each computer
foreach($computer in $computerList) {
#if computer responds to ping
if(Test-Connection $computer -Count 2 -Quiet -ErrorAction SilentlyContinue) {
#disable firewall
Invoke-Command -ComputerName $computer -ScriptBlock {
netsh advfirewall set allprofiles state off
} | Out-Null
#store retrieved authorized programs list in an array
$programs = Invoke-Command -ComputerName $computer -ScriptBlock {
(Get-NetFirewallApplicationFilter).Program
}
#build results object and add it to results array
$results += [PSCustomObject]#{
ComputerName = $computer
Programs = $programs -join ";"
}
} else {
#build results object and add it to offline array
$offline += [PSCustomObject]#{
ComputerName = $computer
Status = "OFFLINE"
}
}
}
#export results to files
$results | Out-File "report.txt"
$offline | Out-File "offline.txt"
I am trying to make a powershell script that gets computer names from a txt file, checks the registry to see what the current version of Flash is installed, and if it is less than 18.0.0.203, run an uninstall exe. Here is what I have been trying:
# Retrieve computer names
$Computers = Get-Content C:\Users\araff\Desktop\FlashUpdater\Servers.txt
# Select only the name from the output
#$Computers = $Computers | Select-Object -ExpandProperty Name
#Sets command to execute if the version is not 18.0.0.203
$command = #'
cmd.exe /C uninstall_flash_player.exe -uninstall
'#
#Iterate through each computer and execute the command if the version is not 18.0.0.203
[Array]$Collection = foreach ($Computer in $Computers){
$AD = Get-ADComputer $computer -Properties LastLogonDate
$ping = Test-Connection -quiet -computername $computer -Count 2
$datetime = Get-Date
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computer)
$RegKey= $Reg.OpenSubKey("SOFTWARE\Macromedia\FlashPlayerActiveX")
$version = $RegKey.GetValue("Version")
if ($version -eq '= 18.0.0.203') {
$installed = "Flash is up to date!"
}
Else {
$installed = "Removing old version..."
Invoke-Expression -Command:$command
}
New-Object -TypeName PSObject -Property #{
TimeStamp = $datetime
ComputerName = $computer
Installed = $installed
OnlineStatus = $ping
LastLogonDate = $AD.LastLogonDate
} | Select-Object TimeStamp, ComputerName, Installed, OnlineStatus, LastLogonDate
}
#Exports csv
$Collection | Export-Csv FlashUpdaterOutput.csv -NoTypeInformation
It exports the CSV just fine, but all the installed columns say "Removing" even if it is the current version. Any ideas on what I am doing wrong? Thanks!
Rather than opening a remote registry key and running cmd /c why not make a [scriptblock] and pipe it to Invoke-Command.
AsJob it and come back for the results later.
[scriptblock]$code = {
$uninst = gci "C:\Windows\System32\Macromed\Flash" -Filter "*.exe" | ?{ $_.Name -ne "FlashUtil64_18_0_0_204_ActiveX.exe" -and $_.Name -like "FlashUtil64_18_0_0_*_ActiveX.exe" }
foreach($flash in $uninst) {
Write-Host $flash.FullName
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo.FileName = $flash.FullName
$proc.StartInfo.Arguments = "-uninstall"
$proc.Start()
$proc.WaitForExit()
Write-Host ("Uninstalling {0} from {1}" -f $flash.BaseName,$env:COMPUTERNAME)
}
}
Invoke-Command -ScriptBlock $code -ComputerName frmxncsge01 -AsJob
Then later just come back and Get-Job | Receive-Job -Keep and see the results.