Simple powershell success or fail log - powershell

I'm looking for the easiest way to write the success or failure of a scheduled script to a csv file. I am not looking for a way to log the error or reason of failure just a simple "Succes/Failure". For this reason i'm not trying to fix/log non-terminating errors.
I thought the easiest way to put every script in a
try {
Write-Host "test"
}
catch{
Code to write to csv here
}
Block where I write to a csv file in the Catch part. Is there a better/easier way to do this or is this the right way to go?

Continuing from my comment. . .
Honestly, this really depends on the situation, i.e. what is you're trying to accomplish. So, let's make up a scenario of querying a computer for some basic info:
Function Get-SystemInfo {
Param (
[Parameter(Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Begin {
$ErrorActionPreference = 'SilentlyContinue'
}
Process {
foreach ($Computer in $ComputerName)
{
try {
# attempt cim session with -EA Stop to not allow it to go
# any further and we can calculate the results.
$CIMSession = New-CimSession -ComputerName $Computer -ErrorAction Stop
$Status = $true
# Perform the actual queries
$CS = Get-CimInstance -ClassName Win32_COmputerSystem -CimSession $CIMSession
$BS = Get-CimInstance -ClassName Win32_BIOS -CimSession $CIMSession
# evaluate against the returned objects
$UserName = if ($CS.UserName -eq $null) { 'No User Logged in' } else { $CS.UserName }
$Manufacturer = if ($CS.Manufacturer -eq $null) { 'N/a' } else { $CS.Manufacturer }
$Model = if ($CS.Model -eq $null) { 'N/a' } else { $CS.Model }
$SerialNumber = if ($BS.SerialNumber -eq $null) { 'N/a' } else { $BS.SerialNumber }
}
catch {
# Set the variables to $null
$Status = $false
$UserName = $null
$Manufacturer = $null
$Model = $null
$SerialNumber = $null
}
finally {
# Output the filled variables
[PSCustomObject] #{
ComputerName = $Computer
Connected = $Status
UserLoggedIn = $UserName
Manufacturer = $Manufacturer
Model = $Model
SerialNumber = $SerialNumber
}
}
}
}
End {
# cleanup
# some people argue this should be in the finally block
# to disconnect any machine, but looking at this from both
# sides, they both have pros/cons.
Get-CimSession | Remove-CimSession
}
}
... the biggest take-away from this quick function, is the -ErrorAction Stop while trying to create a CIM session. This is where looking at the bigger picture comes into play. If you are unable to connect to the computer, why bother continuing? This includes getting an echo reply from a quick ping since that doesn't dictate that you can connect to the remote PC just because you got a reply.
The rest is the if, and else statements that handle the light work evaluating against the returned objects for more control over the output.
Results would be:
PS C:\Users\Abraham> Get-SystemInfo
ComputerName : OER
Connected : True
UserLoggedIn : Abraham
Manufacturer : LENOVO
Model : 22251
SerialNumber : 55555555
PS C:\Users\Abraham> Get-SystemInfo -ComputerName BogusComputerName
ComputerName : BogusComputerName
Connected : False
UserLoggedIn :
Manufacturer :
Model :
SerialNumber :

Related

Powershell lookup RAM information on remote computer and save Partnumbers in to diffent variable

I have a simple script that can pull RAM partnumbers from a remote computer and search google for it. But it does not work as intended. If there's only 1 RAM module installed in the remote computer, it works great google opens with the search result for the Partnumber, yay!.
if there are more than 1 RAM module installed in the remote computer, the first Partnumber in the variable gets searched for in Google. The 2'nd, 3'rd, 4'th partnumber gets typed in to Chrome tab 2,3,4 as an address.
How can I get Chrome to search for all Partnumbers via Google?
My script:
$ComputerName = Read-Host "Write Computer Name"
Get-WmiObject Win32_PhysicalMemory -computername $ComputerName
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {$Partnumber = Get-WmiObject Win32_PhysicalMemory -computername $ComputerName | select -expandproperty Partnumber
Start-Process "chrome.exe" ("https://www.google.com/search?q=$Partnumber")}
if ($ToChrome -eq 'n') {Continue}
That is because chrome.exe interprets the space between the part numbers as new addresses.
I took the liberty to pimp the script with try&catch,a logfile output and the computername as a parameter so that you can call it as Get-MemoryPropertyAndSearchWithGoogle.ps1 -ComputerName ComputerName1
For my testing I used the attribute DeviceLocator as my PartNumber was empty.
#Get-MemoryPropertyAndSearchWithGoogle.ps1
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$ComputerName
)
$ErrorPreference='Stop'
$ErrorActionPreference='Stop'
$LogFilePath = "C:\Temp\$((Get-Date).ToString("yyyy-MM-dd"))$($ComputerName)Get-MemoryPropertyAndSearchWithGoogle.log"
[string]$LogFileString = ""
#$Property = "PartNumber"
$Property = "DeviceLocator"
$ErrorExists = $false
$ComputerMemoryObjects = #()
try
{
$ComputerMemoryObjects = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName -Property *
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#Get-WmiObject Win32_PhysicalMemory -ComputerName $($ComputerName)`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
[string]$SearchString = ""
foreach ($SingleComputerMemoryObject in $ComputerMemoryObjects)
{
if ($SearchString)
{
$SearchString += "+OR+"
}
$SearchString += "$($SingleComputerMemoryObject.$Property)"
}
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y')
{
if ($SearchString)
{
try
{
Start-Process "chrome.exe" ("https://www.google.com/search?q=$($SearchString)")
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#chrome.exe started with searchstring:`"$($SearchString)`"`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
}
else
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#`$SearchString is empty`n"
}
}
if (!($ErrorExists))
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#ScriptCompletedWithoutErrors`n"
}
$LogFileString | Out-File $LogFilePath
$LogFileString
You get multiple objects from Get-WmiObject. You need a loop if you want to do something for each of them.
Also, URL-encoding things that you put into a URL is a good idea. and maybe putting it in double-quotes won't hurt.
Add-Type -AssemblyName System.Web # for [System.Web.HttpUtility]::UrlEncode()
$ComputerName = Read-Host "Write Computer Name"
$installed_memory = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object Manufacturer,PartNumber,SerialNumber,DeviceLocator,Capacity
$installed_memory | Format-Table -AutoSize
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {
$unique_numbers = $installed_memory.Partnumber.Trim() | Sort-Object -Unique
foreach ($number in $unique_numbers) {
$query = [System.Web.HttpUtility]::UrlEncode('"' + $number + '"')
Start-Process chrome.exe "https://www.google.com/search?q=$query"
}
}
Powershell has a handy convenience feature: When you have an array of objects, you can query nested properties from all of them in one go.
For example, if there are 4 Win32_PhysicalMemory objects in $installed_memory, then
$installed_memory.Partnumber.Trim()
gives you 4 readily trimmed part numbers in a single step.

Try/Catch or If/Else?

Hello Guys im having trouble trying to figure out how to make this script to work, im very new on scripting but i do understand most of it but still figuring out some things.
try {
Test-Connection -Computername $_ -count 1 -ErrorAction Stop
} catch {
$_.Exception.ErrorCode -eq 0x800706ba
} `
{
$err = 'Unavailable (Host Offline or Firewall)'
}
try {
Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString)
} catch {
$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException'
} `
{
$err = 'Access denied (Check User Permissions)'
}
Write-Warning "$computer- $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
What im looking for is for this script to test if the system responds or not. If True then next step would be to test credentials, and last would be to perform a get-wmiobject query. But if the system does not respond to ping then i want to catch the hostname that failed to respond ping, capture it and export it to a txt and do the same if the credential fails.
try..catch is for handling terminating errors. Don't abuse it for status checks by forcing a check to fail hard when it doesn't need to. If you just want to test the availability of a system run Test-Connection with the parameter -Quiet as an if condition:
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
...
}
If you need to cascade multiple checks you could do so in a more readable manner by inverting the checks and returning with an appropriate message:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
return "Host $_ unavailable."
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
return 'Login failed for user testuser.'
}
...
}
If you want the information about ping or login failures in log files you can just append it to the respective files:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
$_ | Add-Content 'C:\path\to\unavailable.log'
return
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
$_ | Add-Content 'C:\path\to\login_failure.log'
return
}
...
}
Personally, I can't stand the behavior of Test-Connection. Throwing an exception when it doesn't successfully ping isn't the behavior I want. Like, ever. I understand why they did it that way, but it's not how I ever want a ping to work. Test-Path doesn't throw an exception when the path is invalid. It just returns false. Why is Test-Connection so unfriendly?
WMI allows you to capture the actual status code, and it also allows you to easily control the timeout so it will function much more quickly.
I tend to use this:
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000";
if ($Ping.StatusCode -eq 0) {
# Success
}
else {
# Failure
}
If I actually want to decode the ping status code:
$StatusCodes = #{
[uint32]0 = 'Success';
[uint32]11001 = 'Buffer Too Small';
[uint32]11002 = 'Destination Net Unreachable';
[uint32]11003 = 'Destination Host Unreachable';
[uint32]11004 = 'Destination Protocol Unreachable';
[uint32]11005 = 'Destination Port Unreachable';
[uint32]11006 = 'No Resources';
[uint32]11007 = 'Bad Option';
[uint32]11008 = 'Hardware Error';
[uint32]11009 = 'Packet Too Big';
[uint32]11010 = 'Request Timed Out';
[uint32]11011 = 'Bad Request';
[uint32]11012 = 'Bad Route';
[uint32]11013 = 'TimeToLive Expired Transit';
[uint32]11014 = 'TimeToLive Expired Reassembly';
[uint32]11015 = 'Parameter Problem';
[uint32]11016 = 'Source Quench';
[uint32]11017 = 'Option Too Big';
[uint32]11018 = 'Bad Destination';
[uint32]11032 = 'Negotiating IPSEC';
[uint32]11050 = 'General Failure'
};
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000"
$StatusCodes[$Ping.StatusCode];
You could do it like this:
if(Test-Connection -Computername $_ -Count 2 -ErrorAction 0 -Quiet) {
if(-not (Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString))) {
$err = "Access denied (Check User Permissions)"
}
} else {
$err = "Unavailable (Host Offline or Firewall)"
}
if($err) {
Write-Warning "$computer - $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
}
I believe Test-Connection and Test-Credentials are meant to return $true or $false rather than an exception (if properly used), so you don't really need try/catch here.

Issues automating printer driver update (printer settings) and printer preferences in Win7, using a PS,cmd,vbs,etc script?

WMI can do it, but I have an issue, PCs are on, but logged off. If I try to run:
wmic /node:%strIP% printer where DeviceID="lp1" set DriverName="Lexmark Universal v2"
It fails with a message about a "generic failure". I RDP in and then run the same command from my end, and it works. Powershell version I am using is older, so it does not have some of the printer cmdlets, and updating PS is currently out of the question. Is there a way to remotely log someone in, without actually having to RDP in? Via PS, cmd, PSEXEC, etc?
The other avenue I've taken is using regedit, but I'm hitting some hicups with that, namely that I cannot figure out what to copy. In regedit, I can change the drivername and the setting that enable duplex and tray2 (in printer settings), but I cannot figure how to change the settings in printer preferences for printing double sided and doing so along the long edge.
What I did to figure out what to change, I did a find on the printer name in regedit as a data value and exported the keys before changing the settings. Then I exported it again AFTER changing the settings. I then used fc /c /a /u before.reg after.reg to get the changes. I chopped up the .reg to include only the changed values. Running the .reg seems to change everything, but the print both sides, along the long edge settings. It is a lexmark printer, so I am wondering if maybe preferences for it are stored elsewhere.
This is my most up to date PS1 script. I've commented out some lines as I tried different ways of doing things:
$Cred = Get-Credential
$Str = Read-Host "Please select a site ID [###] "
$PC = Read-Host "Please select a PC number [##] "
Clear-Host
$PCNm = "$Str-CCPC-$PC"
function Test-PsRemoting
{
try
{
$errorActionPreference = "Stop"
$result = Invoke-Command -ComputerName $PCNm { 1 }
}
catch
{
Write-Verbose $_
return $false
}
if($result -ne 1)
{
Write-Verbose "Remoting to $PCNm returned an unexpected result."
return $false
}
$true
}
If(!(Test-PsRemoting)){
PSEXEC \\$PCNm powershell Enable-PSRemoting -force 2>&1 >nul
Clear-Host
Write-Host "Enabled PsRemoting"
}else{Write-Host "PsRemoting already enabled"}
Invoke-Command -ComputerName $PCNm -Credential $Cred -ScriptBlock {
#$lp1 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
$lp1 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp1"}
$lp1.Scope.Options.EnablePrivileges = $true
$lp1.DriverName = "Lexmark Universal v2"
$lp1R = $lp1.Put()
#$lp2 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp2'"
$lp2 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp2"}
$lp2.Scope.Options.EnablePrivileges = $true
$lp2.DriverName = "Lexmark Universal v2"
$lp2R = $lp2.Put()
}
#$lp1 = Get-WMIObject -Impersonation Delegate -Authentication Call -Credential $Cred -ComputerName $PCNm -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
#$lp1.DriverName = "Lexmark Universal v2"
#$lp1.Put()
No matter which way I try it, invoke-command, or get-wmiobject, I get:
Exception calling "Put" with "0" argument(s): "Generic failure "
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
+ PSComputerName : 150-CCPC-02
This doesn't particularly answer your actual question but as a solution for how I do this very same thing I thought I would give you what I threw together to update printer properties. I have not cleaned this up at all as I was porting it from my create printer function.
Function Set-SSPrinter {
Param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[string]$PortName,
[string]$DriverName,
[string]$Comment,
[string]$Location,
[bool]$Shared,
[string]$ShareName = $Name,
[string]$PermissionSDDL,
[string]$PrintProcessor,
[string]$DataType,
[bool]$RawOnly
)
try {
$modprinter = Get-WmiObject Win32_Printer -ComputerName $ComputerName | ?{$_.name -eq $Name}
$modprinter.Scope.Options.EnablePrivileges = $true
if($DriverName) {
$modprinter.DriverName = $DriverName
}
if($PortName) {
$modprinter.PortName = $PortName
}
if($Shared) {
$modprinter.Shared = $Shared
}
if($ShareName) {
$modprinter.ShareName = $ShareName
}
if($Location) {
$modprinter.Location = $Location
}
if($Comment) {
$modprinter.Comment = $Comment
}
if($Name) {
$modprinter.DeviceID = $Name
}
if($PrintProcessor) {
$modprinter.PrintProcessor = $PrintProcessor
}
if($DataType) {
$modprinter.PrintJobDataType = $DataType
}
if($RawOnly) {
$modprinter.RawOnly = $RawOnly
}
$result = $modprinter.Put()
if($PermissionSDDL) {
$modprinter.SetSecurityDescriptor($objHelper.SDDLToWin32SD($PermissionSDDL).Descriptor) | Out-Null
}
$("Update Complete: " + $Name)
} catch {
$("Update Failed: " + $Name)
Write-Warning $_.Exception.Message
$error.Clear()
}
}
Unfortunately I use the printer name to figure out which device to modify on the remote machine. Your executing credentials from the powershell session you have open must have admin rights on the remote machine. if necessary do a runas different user on powershell.exe
Example usage:
Set-SSPrinter -ComputerName "10.210.20.100" -Name "TestPrinter" -DriverName "Lexmark Universal v2"
wmic /node:servername /user:username /password:password path win32_something call methodname
Is how to do it.
Things with users are best done with logon scripts because that is how windows is designed.

Check for net.tcp binding in PowerShell

I am configuring a process that checks IIS settings on a Web Server for net.tcp bindings for a particual Web Site, and if it does not exist, create it. I have this chunk of code to check
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {
if ($Site.name -eq "LOSSI") {
$Binding = $Site.bindings
foreach ($bind in $Binding.collection) {
if ($bind -eq "net.tcp 443:*")
{
Write-Host $bind
}
}
}
}
But I never fall into the last conditional. I have validated by hand that the binding is set to
LOSSI
3
Started
D:\LOSSI
http *:63211: net.tcp 443:
I imagine I am doing something silly wrong, but I cannot figure it out. Is there an easier way to check a website for tcp binding?
function Test-TcpPort {
<#
.SYNOPSIS
Determine if computers have the specified ports open.
.EXAMPLE
PS C:\> Test-TcpPort -ComputerName web01,sql01,dc01 -Port 5985,5986,80,8080,443
.NOTE
Example function from PowerShell Deep Dives 2013.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("CN","Server","__Server","IPAddress")]
[string[]]$ComputerName = $env:COMPUTERNAME,
[int[]]$Port = 23,
[int]$Timeout = 5000
)
Process {
foreach ($computer in $ComputerName) {
foreach ($p in $port) {
Write-Verbose ("Checking port {0} on {1}" -f $computer, $p)
$tcpClient = New-Object System.Net.Sockets.TCPClient
$async = $tcpClient.BeginConnect($computer, $p, $null, $null)
$wait = $async.AsyncWaitHandle.WaitOne($TimeOut, $false)
if(-not $Wait) {
[PSCustomObject]#{
Computername = $ComputerName
Port = $P
State = 'Closed'
Notes = 'Connection timed out'
}
} else {
try {
$tcpClient.EndConnect($async)
[PSCustomObject]#{
Computername = $computer
Port = $p
State = 'Open'
Notes = $null
}
} catch {
[PSCustomObject]#{
Computername = $computer
Port = $p
State = 'Closed'
Notes = ("{0}" -f $_.Exception.Message)
}
}
}
}
}
}
}
Microsoft reference script for check port
add this function in
for powershell 64 bit
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
for powershell 32 bit
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\profile.ps1
then open powershell use this
Test-TCPPort google.com -Port 80
output :
True

How can I automate Telnet port checking in Powershell?`

I'm currently trying to put together a script that queries AD for a list of computers, pings the computers to determine which ones are still active, and then telnets into a specific port on all the pingable computers. The output I'm looking for is a full list of pingable computers in AD for which I can't telnet to the said port.
I've read these few questions, but they don't quite hit on what I'm trying to do. I just want to see if the telnet connection is successful without entering telnet (or automate the quitting of telnet) and move on to the next machine to test. The AD and pinging portions of my script are set, I'm just stuck here. The things I've tried haven't quite worked as planned.
Here is the code for the first parts of the script, if needed:
Get-ADComputer -Filter * -SearchBase 'DC=hahaha,DC=hehehe' | ForEach {
$computerName = $_.Name
$props = #{
ComputerName = $computerName
Alive = $false
PortOpen = $false
}
If (Test-Connection -ComputerName $computerName -Count 1 -Quiet) {
$props.Alive = $true
}
Adapting this code into your own would be the easiest way. This code sample comes from the PowerShellAdmin wiki. Collect the computer and port you want to check. Then attempt to make a connection to that computer on each port using Net.Sockets.TcpClient.
foreach ($Computer in $ComputerName) {
foreach ($Port in $Ports) {
# Create a Net.Sockets.TcpClient object to use for
# checking for open TCP ports.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Computer, $Port)
# Make error messages visible again
$ErrorActionPreference = 'Continue'
# Determine if we are connected.
if ($Socket.Connected) {
"${Computer}: Port $Port is open"
$Socket.Close()
}
else {
"${Computer}: Port $Port is closed or filtered"
}
# Apparently resetting the variable between iterations is necessary.
$Socket = $null
}
}
Here is a complete powershell script that will:
1. read the host and port details from CSV file
2. perform telnet test
3. write the output with the test status to another CSV file
checklist.csv
remoteHost,port
localhost,80
asdfadsf,83
localhost,135
telnet_test.ps1
$checklist = import-csv checklist.csv
$OutArray = #()
Import-Csv checklist.csv |`
ForEach-Object {
try {
$rh = $_.remoteHost
$p = $_.port
$socket = new-object System.Net.Sockets.TcpClient($rh, $p)
} catch [Exception] {
$myobj = "" | Select "remoteHost", "port", "status"
$myobj.remoteHost = $rh
$myobj.port = $p
$myobj.status = "failed"
Write-Host $myobj
$outarray += $myobj
$myobj = $null
return
}
$myobj = "" | Select "remoteHost", "port", "status"
$myobj.remoteHost = $rh
$myobj.port = $p
$myobj.status = "success"
Write-Host $myobj
$outarray += $myobj
$myobj = $null
return
}
$outarray | export-csv -path "result.csv" -NoTypeInformation
result.csv
"remoteHost","port","status"
"localhost","80","failed"
"asdfadsf","83","failed"
"localhost","135","success"