Powershell output logging when using a text file to gather server names - powershell

Have a bit of an issue whereby would like to figure out the best way to handle success or failures. Have a powershell query which checks the dcom port range, if it is within the specified value output to a success file, if not a failure file. The issue is, it seems to be outputting the entire serverlist.txt for a success and need to know a way to break this down so it only appends a server (either success/failure) to it, not all at once.
Here is the powershell script contents:
powershell -executionpolicy bypass .\DCOMPortRange.ps1
Where DCOMPortRange.ps1 contains
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
$val = (Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports
if($val -eq "50000-50500")
{
Write-Output "$computername" | out-file C:\folderpath\Success.log -append
} Else {
Write-Output "$computername" | out-file C:\folderpath\Failure.log -append
}
The issue is the error path lets say is a success it appends the entire server list.
Please advise?

This is how I would do it. This does require that you do have PSremoting enabled on the servers
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
ForEach ($server in $computername) {
$val = Invoke-Command -Computername $server -ScriptBlock {(Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports}
if ($val -ge 50000 -and $val -le 50500) {
Write-Output "$server" | out-file C:\folderpath\Success.log -append
}
Else {
Write-Output "$server" | out-file C:\folderpath\Failure.log -append
}
}
Edit: A change to the if statement
/Anders

$remotecomputername = #("PC1","PC2","RealServerName")
ForEach ($computer in $remotecomputername) {
Invoke-Command -Computername $computer -ScriptBlock { $val = (Get-
ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -
ExpandProperty Ports} }
if($val -eq "50000-50500") {
write-host $computer DCOM Port in Range
} else {
write-host $computer DCOM Port not in range
}

Related

How to save Powershell output to specific folder on the local machine when running an Invoke command on remote machine

I am using powershell to pull basic computer information from all computers on a LAN. These computers are not, and will never be on, a domain. I have had some success in my test runs getting the output for all of the machines to save into the c:\scripts folder on the host machine. I am, however, having to use the Invoke-command for every cmdlet so that I can put the Output destination outside of the {}.
$computers = Get-Content -Path 'c:\scripts\livePCs.txt'
foreach ($computer in $computers) {
$username = '$computer\Administrator'
$password = Get-Content 'C:\scripts\encrypted_password.txt' | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
# Local Mounted Shares Enumeration
Invoke-Command -Computername $computer -Credential $credential {Get-SmbMapping | Format-Table -AutoSize} | Out-File "c:\scripts\ComputerInformation\$computer.mountedshares.ps.txt"
# Local Shares Enumeration
Invoke-Command -Computername $computer -Credential $credential {Get-SmbShare | Format-Table -AutoSize} | Out-File "c:\scripts\ComputerInformation\$computer.localshares.ps.txt"
I would prefer not to have to do this and it becomes problematic when I have to use If/Else statements, where, because I cannot put the destination outside of braces, I get a file cannot be found error (since it is trying to save on the remote host). I tried using a share instead, in the below but now am getting an access to the file path is denied error.
Invoke-Command -Computername $computer -Credential $credential {
$computername = hostname.exe
If ($PSVersionTable.PSVersion -ge '4.0') {
If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Get-WindowsOptionalFeature -Online -FeatureName *Hyper-V* | Format-Table -AutoSize | Out-File -Width 1024 "\\$env:computername\scripts\ComputerInformation\$computername.hyperv.admin.ps.txt"
if (Get-Command Get-VM -ErrorAction SilentlyContinue) {
Get-VM | Format-Table -AutoSize | Out-File -Width 1024 -Append "c:\scripts\ComputerInformation\$computername.hyperv.admin.ps.txt"
Get-VM | Get-VMNetworkAdapter | Format-Table -AutoSize | Out-File -Width 1024 -Append "c:\scripts\ComputerInformation\$computername.hyperv.admin.ps.txt"
} else {
Write-Host -ForegroundColor Yellow " Hyper-V feature not installed on this host"
}
} else {
Write-Host -ForegroundColor Red " You do not have required permissions to complete this task ..."
}
} else {
Write-Host -ForegroundColor Red " This commands requires at least PowerShell 4.0 ... manual inspection is required"
}
How do I run this one a remote machine using Invoke-Command but save the output to the local c:\scripts folder?
If the goal is to give yourself the option to output to multiple files on the calling system, you could use a hash table ($results) inside of your script block to store your results. Then output that table at the end of your script block. Based on those keys/values, you could output to file.
foreach ($computer in $computers) {
$Output = Invoke-Command -Computername $computer -Credential $credential {
$results = #{}
$computername = hostname.exe
If ($PSVersionTable.PSVersion -ge '4.0') {
If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
$HyperV = Get-WindowsOptionalFeature -Online -FeatureName *Hyper-V* | Format-Table -AutoSize
if (Get-Command Get-VM -ErrorAction SilentlyContinue) {
$VMInfo = Get-VM | Format-Table -AutoSize
$VMNic = Get-VM | Get-VMNetworkAdapter | Format-Table -AutoSize
} else {
Write-Host -ForegroundColor Yellow " Hyper-V feature not installed on this host"
}
} else {
Write-Host -ForegroundColor Red " You do not have required permissions to complete this task ..."
}
} else {
Write-Host -ForegroundColor Red " This commands requires at least PowerShell 4.0 ... manual inspection is required"
}
$results.Add('HyperV',$HyperV)
$results.Add('VMInfo',$VMInfo)
$results.Add('VMNic',$VMNic)
$results
}
$Output.HyperV | Out-File -Width 1024 "c:\scripts\ComputerInformation\$computer.hyperv.txt"
$Output.VMInfo | Out-File -Width 1024 "c:\scripts\ComputerInformation\$computer.VMInfo.txt"
$Output.VMNic | Out-File -Width 1024 "c:\scripts\ComputerInformation\$computer.VMNic.txt"
}
If the goal is to simply output all data to one location, you can simply store your Invoke-Command result into a variable. Then write the variable contents to file:
$Output = Invoke-Command -Computername $computer -Scriptblock { # my code runs here }
$Output | Out-File "C:\Folder\$computer.txt"
If you are looking to capture Write-Host output in a variable, you will need to send the information stream to the success stream ( { script block } 6>&1 }
You can redirect the output
$YourScriptBlock = { your script }
$result = Invoke-Command -ScriptBlock $YourScriptBlock 4>&1 -Computer $c -Credential $c
Afterwards the contents are in $result
write-output $result

Slow Processing Script in Powershell, Worklfow first steps

as i was wondering why my script takes so long i was seachring on google and also here in stackoverflow.
But all that i could find any close to helpful was this one here, Powershell Script Running Slowly
As I'm still pretty new to Powershell this is a little complicated to get through and take over to my script as i dont know how to handle those mentiond things anyway as i never heard of it before.
My Script is pretty easy and just gives me some Informations if there is something that returns an echo or not.
I wanted to "scan" our entire Network so I made an csv with out local Networks IP's and pass it to Powershell to "Ping" those.
But I realised that the "was not responing" part takes a long time to execute.
$list = Import-Csv -Path D:\ipcheck3.csv -UseCulture
$x=$list.IP
$ErrorActionPreference = "SilentlyContinue"
foreach ($y in $x)
{
try
{
if(Test-Connection $y -Count 1 -quiet)
{
write-host "$y responded"
$y | Export-Csv -Path D:\PingSucceded.csv -Append
}
else
{
Write-Host "$y was not responding"
$y | Export-Csv -Path D:\Pingfailed.csv -Append
}
}
catch
{
Write-Warning "Other Error occured"
}
}
There are not only Windows Clients out there so WMI is not an option and I don't know how to achvie this otherwise
EDIT:
After the Workflow input this is my "Try Version"
workflow Test-IPrange
{
Param
(
$IPs
)
$tocheck= $IPs.IP
foreach -parallel ($IP in $tocheck)
{
$pingsucceed = Test-Connection $IP -Count 1 -quiet
if($pingsucceed -eq "True")
{
$IP | Export-Csv -Path D:\testj.csv -Append
}
else
{
$IP | Export-Csv -Path D:\testn.csv -Append
}
}
}
Test-IPrange -IPs $(Import-Csv -Path D:\ipcheck3.csv -UseCulture)
My Output of Workflow Try
#TYPE System.String
PSComputerName,"PSShowComputerName","PSSourceJobInstanceId","Length"
localhost,"True","4e208e38-f7c2-492f-9d81-6583a103c3ac","12"
localhost,"True","4e208e38-f7c2-492f-9d81-6583a103c3ac","12"
With the Help of #Fourat
i edited my code to this form
Function Custom-Ping {
Param(
[string]$Address
)
$ping = ping $Address /w 1 /n 1
$result = ![string]::IsNullOrEmpty($ping -Like "*(0% Verlust)*")
return $result
}
$list = Import-Csv -Path D:\ipcheck3.csv -UseCulture
$x=$list.IP
$ErrorActionPreference = "SilentlyContinue"
foreach ($y in $x)
{
try
{
if(Custom-Ping $y)
{
Write-Host "$y responded"
$y | Export-Csv -Path D:\PingsuccededV3.csv -Append
}
else
{
Write-Host "$y was not responding"
$y | Export-Csv -Path D:\PingfailedV3.csv -Append
}
}
catch
{
Write-Warning "Textline from CMD Command or other Error"
}
}
which works properly good and is faster
I think that your process time is spoiled by the timeouts. If all your IPs are in the local network, try to reduce the timeout (because the default value is 5 seconds).
If you have Powershell 6 :
Test-Connection $y -Count 1 -quiet -TimeoutSeconds 1
If you don't, just use ping :
ping 58.47.45.1 /w 1 /n 1
You can also use a parallel for each loop, but it won't help much if you have multiple fails :
ForEach -Parallel ($x in $y)
{
...
}
UPDATE
In order to handle ping results, you can use a function like this (I used the keyword 'perte' because my computer is in French) :
Function Custom-Ping {
Param(
[string]$Address
)
$ping = ping $Address /w 1 /n 1
$result = ![string]::IsNullOrEmpty($ping -Like "*(perte 0%)*")
return $result
}
I've used Workflow to solve this issue my self. It's a few years ago I did it, so something better and newer is out there. But this works great for me...
I've ping over 2000 computers within a few Min...
workflow Test-ComputersConnection
{
Param
(
# Param1 help description
$Computernames#,
# Param2 help description
# [int]
# $Param2
)
foreach -parallel ($ComputerName in $Computernames)
{
$ConnectionTest = Test-Connection -ComputerName $ComputerName -ErrorAction SilentlyContinue -Count 1
if ($ConnectionTest.Address -eq $ComputerName) {
Write-Output $(Add-Member -MemberType NoteProperty -Name "Computername" -Value $ComputerName -InputObject $ConnectionTest -PassThru )
#Write-Verbose -Verbose -Message "[$($ComputerName)]: Replays on Ping."
}
Else {
#Write-Verbose -Verbose -Message "[$($ComputerName)]: Do not replays on Ping."
}
}
}
$OnlineNow0 = Test-ComputersConnection -Computernames $( Import-Csv -Path D:\ipcheck3.csv -UseCulture |
Select-Object -ExpandProperty name)
The code above is a quick edit of what I use... You will need to edit the $(Import ...) statement first, to make sure the PC name is being deliveret to the workflow.
I've just testet on my own computer and it gave me a reply...

Powershell ForEach loop with embedded IF statements

Starting to write powershell scripts (very new) because SCCM tends to respond better to them (both client and server)
So with the above stated here is my first script:
#Changes the 'ProvisioningMode' Key in the registry to False
$ProvisiongMode = New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
#Clears or 'nulls' the SystemTaskExcludes key in the registry
$SystemTaskExludes = New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
#----------------------------------------------------------------------------------------------
$Success = "C:\Path\to.log"
$Failure = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
$SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode
$Online = Test-Conntection -Computername $ComputerName -Count 1 -Quiet
ForEach ($ComputerName in $Computers)
if ($Online -eq 'False')
{
Write-Output $ComputerName`t'Connection Failed' >> $Failure
}
Else
{
if ($SearchStr -eq True)
{
$ProvisioningMode
$SystemTaskExcludes
}
}
#Second Check
if ($SearchStr -eq 'False')
{
Write-Output $ComputerName`t'Registry has been changed' >> $Success
}
The issue in question is the $Online variable. I would like to see if a computer is responsive to ping, if true then proceed to run $ProvisioningMode and $SystemTaskExclude.
Then the other issue is querying that key to see if it changed. The issue with that one is $SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode returns
ProvisionMode
-----------------
False
And I cant grab just the false data.
Like I stated; very new at powershell and writing something that I will use helps me learn.
Edit: What I Have tried is
ForEach ($Name in $Computers)
{
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet | Write-Output $Online
}
if ($Online -eq 'True') {Write-Output $Name`t'Computer is online' >> C:\Online.txt}
And many variations of the same thing.
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
Returns Data, which is what I want, but I need to input that into an If statement and still retain the $Name for the $StringStr and log files.
Those of you wondering, this takes the client out of provisioning mode when running an OSD. It fixes the 'No Self-Signed Certificate' issue.
Even though the string representations of boolean values in PowerShell are True and False, the correct way to compare againt such a value is with the $true and $false variables.
Furthermore, assign the result of Test-Connection to $Online with =:
$Online = Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
if($Online -eq $true){
# Machine responds to ping, do stuff!
}
But the comparison is actually unnecessary. If $Online already equals $frue or $false, you can use it on its own inside the if statement:
if($Online){
# Machine responds to ping, do stuff!
}
I assume that $ProvisionMode, $SystemTaskExcludes and $SearchStr are all statements that you want to execute on the remote machine, not on the SCCM server itself.
To do so, you will need to connect to the machine and instruct it to execute the *-ItemProperty statements.
# Enclosing statements in {} creates a ScriptBlock - a piece of code that can be invoked later!
$ProvisionMode = {
#Changes the 'ProvisioningMode' Key in the registry to False
New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
}
$SystemTaskExludes = {
#Clears or 'nulls' the SystemTaskExcludes key in the registry
New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
}
$SearchStr = {
Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | Select-Object -ExpandProperty ProvisioningMode
}
#----------------------------------------------------------------------------------------------
$LogFilePath = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
foreach($Computer in $Computers){
$Online = Test-Connection -Computername $Computer.Name -Count 1 -Quiet
if(-not $Online)
{
"$ComputerName`t'Connection Failed'" | Out-File -FilePath $LogFilePath -Append
}
else
{
$SearchResult = Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SearchStr
if ($SearchResult)
{
# The call operator (&) invokes the scriptblock
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $ProvisionMode
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SystemTaskExludes
}
else # SearchStr must be $false, or non-existing
{
"$ComputerName`t'Registry has been changed'" | Out-File -FilePath $LogFilePath -Append
}
}
}
For simplicity, I've used Invoke-Command with the -ComputerName parameter, but in a real world situation, I would set up a PSSession with New-PSSession, and reuse that for the connection with Invoke-Command -Session

Easily hide error given by PS and show message

im currently finishing my PS script to get the time from a list of servers and export them to a .txt file. Thing is that servers with connection problems gives just a PS error. I want that servers with connection issues get logged also and by just a message i.e "Server server name not reachable". Thanks a lot for your help!
cls
$server = Get-Content srvtime_list.txt
Foreach ($item in $server)
{
net time \\$item | find /I "Local time" >> srvtime_result.txt
}
I'd probably rewrite your code a bit:
Get-Content srvtime_list.txt |
ForEach-Object {
$server = $_
try {
$ErrorActionPreference = 'Stop'
(net time \\$_ 2>$null) -match 'time'
} catch { "Server $server not reachable" }
} |
Out-File -Encoding UTF8 srvtime_result.txt
There are other/better ways to get the time (as others suggested) but to answer your question:
You can suppress errors by redirecting the error stream to null.
Check the $LASTEXITCODE variable, any result other than 0 means the command did not completed successfully.
Get-Content srvtime_list.txt | Foreach-Object{
net time \\$_ 2>$null | find /I "Current time" >> srvtime_result.txt
if($LASTEXITCODE -eq 0)
{
$result >> srvtime_result.txt
}
else
{
"Server '$_' not reachable" >> srvtime_result.txt
}
}
I'd do something like this:
Get-Content srvtime_list.txt | %{
$a = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $_ -erroraction 'silentlycontinue'
if ($a) { "$_ $($a.ConvertToDateTime($a.LocalDateTime))" } else { "Server $_ not reachable" }
} | Set-Content srvtime_result.txt
Use the Test-Connection cmdlet to verify that the remote system is reachable.
cls
$server = Get-Content srvtime_list.txt
Foreach ($item in $server)
{
if (test-connection $item) {
net time \\$item | find /I "Local time" >> srvtime_result.txt
} else {
"$item not reachable" | out-file errors.txt -append
}
}
But you can do this in pure Powershell, without resorting to net time - use WMI. This is untested as I don't have Windows handy at the moment, but it's at least 90% there.
cls
$server = Get-Content srvtime_list.txt
$ServerTimes = #();
Foreach ($item in $server)
{
if (test-connection $item) {
$ServerTimes += Get-WMIObject -computername $name win32_operatingsystem|select systemname,localdatetime
} else {
"$item not reachable" | out-file errors.txt -append
}
}
$ServerTimes |Out-File srvtime_result.txt

start-job Run command in parallel and output result as they arrive

I am trying to get specific KBXXXXXX existence on a list of servers , but once my script one server it takes time and return result and come back and then move to next one . this script works perfectly fine for me .
I want my script to kick off and get-hotfix as job and other process just to collect the results and display them.
$servers = gc .\list.txt
foreach ($server in $servers)
{
$isPatched = (Get-HotFix -ComputerName $server | where HotFixID -eq 'KBxxxxxxx') -ne $null
If ($isPatched)
{
write-host $server + "Exist">> .\patchlist.txt}
Else
{
Write-host $server +"Missing"
$server >> C:\output.txt
}
}
The objective it to make the list execute faster rather than running serially.
With Powershell V2 you can use jobs as in #Andy answer or also in further detail in this link Can Powershell Run Commands in Parallel?
With PowerShell V2 you may also want to check out this script http://gallery.technet.microsoft.com/scriptcenter/Foreach-Parallel-Parallel-a8f3d22b using runspaces
With PowerShell V3 you have the foreach -parallel option.
for example (NB Measure-Command is just there for timing so you could make a comparison)
Workflow Test-My-WF {
param([string[]]$servers)
foreach -parallel ($server in $servers) {
$isPatched = (Get-HotFix -ComputerName $server | where {$_.HotFixID -eq 'KB9s82018'}) -ne $null
If ($isPatched)
{
$server | Out-File -FilePath "c:\temp\_patchlist.txt" -Append
}
Else
{
$server | Out-File -FilePath "c:\temp\_output.txt" -Append
}
}
}
Measure-Command -Expression { Test-My-WF $servers }
For this use PowerShell jobs.
cmdlets:
Get-Job
Receive-Job
Remove-Job
Start-Job
Stop-Job
Wait-Job
Here's an untested example:
$check_hotfix = {
param ($server)
$is_patched = (Get-HotFix -ID 'KBxxxxxxx' -ComputerName $server) -ne $null
if ($is_patched) {
Write-Output ($server + " Exist")
} else {
Write-Output ($server + " Missing")
}
}
foreach ($server in $servers) {
Start-Job -ScriptBlock $check_hotfix -ArgumentList $server | Out-Null
}
Get-Job | Wait-Job | Receive-Job | Set-Content patchlist.txt
Rather than use jobs, use the ability to query multiple computer that's built into the cmdlet. Many of Microsoft's cmdlets, especially those used for system management, take an array of strings as the input for a -Computername parameter. Pass in your list of servers, and the cmdlet will query all of them. Most of the cmdlets that have this ability will query the servers in series, but Invoke-Command will do it in parallel.
I haven't tested this as I don't have Windows booted at the moment, but this should get you started (in sequence).
$servers = gc .\list.txt
$patchedServers = Get-HotFix -ComputerName $servers | where HotFixID -eq 'KBxxxxxxx'|select machinename
$unpatchedServers = compare-object -referenceobject $patchedServers -differenceobject $servers -PassThru
$unpatchedServers |out-file c:\missing.txt;
$patchedServers|out-file c:\patched.txt;
In parallel:
$servers = gc .\list.txt
$patchedServers = invoke-command -computername $servers -scriptblock {Get-HotFix | where HotFixID -eq 'KBxxxxxxx'}|select -expandproperty pscomputername |sort -unique
As before, I don't have the right version of Windows available at the moment to test the above & check the output but it's a starting point.