Printers at my job are in constant flux. Because of this I need a script that can automate the process of adding and deleting printers with little intervention. I have here an example of the code that I am using to perform this task. I'd like to condense this code and add 2 new features, unfortunately my ability to code is few an far between as I am but a humble systems janitor.
New Feature idea's:
If the .vbs files 'Last Modified' date has not been altered since the last time this script was ran, the script immediately stops. A comment outlining a method to override this would be good too.
The log file that shows which servers were created, I'd like to know if they were created -successfully- or if they failed.
If the script comes across an error while adding a network printer, it will immediately skip it, but the log will show that the printer has been added created
<#
.SYNOPSIS
Printer Script
1. Archives the working file if it is greater than or equal to 1 MB
2. Starts logging
3. Removes priners which no longer exist on the print server (because of deletion, moves, etc) in a status of 2
4. Compares the Network printer list to the locally installed Network printers and outputs a list of the printers not installed locally
5. Takes the new printer list, and pings each printer, if ping fails, the printer is removed from the install list
6. Builds new printers
7. Ends logging
8. Performs log file maintenance by archiving and removing old log files
.DESCRIPTION
Date: 04/03/2013
Version: 1
PowerShell Version: 2.0
.INPUTS
Network Printer Script \\ShareDrive\Sharefolder\ShareFile.vbs
.OUTPUTS
Log files
Archive files
Temp text files
ShareFile.txt
pl.txt
Localprinters.txt
NewPrinters.txt
ToDelete.txt
working.txt
.EXAMPLE
C:\scripts\PrinterMaint.ps1
.NOTES
Create the following folders before running this script:
C:\PrinterMaint\
C:\temp\
C:\Scripts\
.LINK
#>
###################
####-VARIABLES-####
###################
# Get computer name
$Server = (gc env:computername)
# Specifies Local Printer Properties to be retrieved
$Props = "Name","PrinterStatus"
$GLOBAL:job1 = ""
# Log file naming convention
$Logfile = "C:\PrinterMaint\"+$(gc env:computername)+".log"
# Archive log naming convention
$archive = "C:\PrinterMaint\Archive_"+(gc env:computername)+"_"+$date2+".log"
# Gets todays date and assigns it to a variable
$date = Get-Date
# temp files and path of text file which holds the names of the printers to be deleted
$DelFile = "C:\temp\ToDelete.txt"
# Creates a formatted date which can be used in a file name
$date2 = Get-Date -format "yyyy-MM-ddTHHmmss"
# Error File
$ErrorFile = "C:\PrinterMaint\Errors.log"
# Handle Errors
$ErrorActionPreference = "silentlycontinue"
# Get computer name
$Server = (gc env:computername)
# Local Printer
$GOBAL:LPrinter = #()
$GLOBAL:ltext = #()
$GLOBAL:FinalPrinters = #()
$GLOBAL:Bad = #()
###################
####-FUNCTIONS-####
###################
Function ServiceShutdown #Shuts down High CPU services while this script runs so as to not interfere
{
Set-Service SAVAdminService -StartupType Disabled
Stop-Service SAVAdminService
Set-Service SavService -StartupType Disabled
Stop-Service SavService
}
Function CycleSpooler
{
Write-Host "Cycling Print Spooler"
Stop-Service spooler
Start-Sleep -seconds 10
Start-Service spooler
Write-Host "Print Spooler Cycle is complete"
}
Function ServiceReenable
{
Set-Service SavService -StartupType Automatic
Start-Service SavService
Set-Service SAVAdminService -StartupType Automatic
Start-Service SAVAdminService
}
Function GetLocalPrinters
{
# Start gathering local printer info
$GLOBAL:job1 = gWmi Win32_Printer -computer $Server -Property $props -AsJob
If ($GLOBAL:job1.State -eq "Running")
{
Cls
Write-Host "The local Printer gather may take several minutes to complete" -ForegroundColor Blue -BackgroundColor Yellow
$i = 0
do {Write-Progress -Activity "Gathering Local Printers..." -status "$i seconds";$i+=5; Start-sleep -seconds 5}
while ($GLOBAL:job1.State -eq "Running")
Write-Progress "Done" "Done" -completed
Else
{Write-Host "Local printer gather has completed" -ForegroundColor Yellow}
}
}
Function ArchiveLog # Archives log files if they are greater than or equal to 1MB
{
# Creates the Variable which will hold the archive log naming convention
if (test-path $Logfile) #If the logfile exists then determine if it needs to be archived
{
# Gets the log file if it is greater than oe equal to 1MB
Get-ChildItem $LogFile -recurse | ? {($_.length/1MB) -ge 1} | % {
# Archives the log File
Move-Item $_.Fullname $archive
}
}
else # If the log file does not exist
{New-Item $Logfile -type File} # Create a new log file
}
Function LogI # Function Called to Write actions to the log file
{
# Accepts the parameter
Param ([string]$logstring)
# new line
"`r`n"
# Logs variable to the log file
$logstring >> $LogFile
}
Function StartLog # Function called to format the beginning of each log file
{
LogI "**********Starting Log**********"
LogI "Log Date: $(Get-Date)"
}
Function EndLog # Function called to format the end of each log file
{
LogI "**********End Log**********"
LogI "Log Date: $(Get-Date)"
}
Function CleanupTMP # Function called to cleanup all temp files created by this script
{
# Cleanup of existing build files
$workingfiles = "C:\temp\ShareFile.txt", "C:\temp\pl.txt", "C:\temp\Localprinters.txt", "C:\temp\NewPrinters.txt", "C:\temp\ToDelete.txt", "C:\temp\working.txt"
foreach ($file in $workingfiles) # Gets each file, deletes it
{
# Determines if the file in the path exists
if (test-path $file)
{
echo Test-Path $file
# If the file does exist, it deletes each one
Remove-Item $file
# Writes the name of the deleted file to the log file
LogI "Deleted File: $date $file"
}
}
}
Function CleanupLogs # Removes Log files older than 14 days old
{
# set folder path
$log_path = "C:\PrinterMaint\Archive*.log"
# set min age of files to 14 days
$max_days = "-14"
# determine how far back we go based on current date
$del_date = $date.AddDays($max_days)
# Gets log files older than the $max_days defiled and Removes them
Get-ChildItem $log_path -Recurse | Where-Object { $_.LastWriteTime -lt $del_date } | Remove-Item
}
Function BadPrinter # Function checks for indtalled printers with a status of 2, "Printer not Found on Server, unable to connect"
{
# Handle Bad Printers
# Gathers Printers with status of 2 - Not Found on Server
$GLOBAL:Bad = $GLOBAL:LPrinters | where {$_.printerstatus -eq 2} | select name | sort name
#$GLOBAL:Bad2 = $GLOBAL:LPrinters | where {$_.printerstatus -eq 3 -and $_.name -ne "Microsoft XPS Document Writer"} | select name | sort name
#$GLOBAL:Bad = $GLOBAL:Bad1 + $GLOBAL:Bad2
if ($GLOBAL:Bad.count -gt 0) # If the array is greater than 0 then call the function to delete the printers
{DelPrinter}
Else
{Write-Host "No printers will be deleted" -ForegroundColor Yellow
LogI "$date No printers were deleted"}
}
Function DelPrinter # Function is called to delete printers if the array tests to be greater than 0
{
# Outputs the printers to a text file
$GLOBAL:Bad | Out-File $DelFile
# Assigns the text file contents to an array
$btext = gc $DelFile
# Removes the text file
#remove-item $DelFile
##########################-Formatting-##########################################
# Remove first four lines from the text file, spaces and blank lines
$btext[0],$btext[3..($btext.length-1)] | out-file $DelFile -encoding ascii
(gc $DelFile) | ? {$_.trim() -ne "" } | sc $DelFile -encoding ascii
(gc $DelFile) | Foreach {$_.TrimEnd()} | sc $DelFile -encoding ascii
(gc $DelFile) | where {$_ -ne ""} | sc $DelFile -encoding ascii
##########################-Formatting-###########################################
# Removing a Network Printer Connection
# Get the contents of the delete text file
$GLOBAL:Bad = (gc $DelFile)
# Reviews each item in the array and processes them seperately
foreach ($B in $Bad)
{
# Reinitializes date for more accurate reporting
$date2 = Get-Date
# Assigns the date and time to the printer being deleted to a variable
$LogDel = "$date2 $B"
# Logs the deletion of the printer
LogI "Deleted: $LogDel"
# Networked printer is now deleted
Write-Host "Deleting printer $B"
(New-Object -ComObject WScript.Network).RemovePrinterConnection("$B")
}
}
Function GNP # Function to Get Network Printers (GNP)
{
# Copies the printer map script to the local computer
Copy-Item \\ShareDrive\Sharefolder\ShareFile.vbs C:\temp\ShareFile.txt
}
Function FNP
{
# Assign the contents of the Network printer list to a variable
$NPrinters = Get-Content C:\temp\ShareFile.txt
##########################-Formatting-##########################################
#removes all text and formatting from the network printer list then assigns the contents to a new array
foreach ($NPrinter in $NPrinters)
{
$NPrinter1 = $NPrinter.split(" ")
$Printers = $NPrinter1[1]
$Printers1 = $($Printers -split '"')
$PL = $Printers1[1]|Where-Object {$_.length -gt 2}
$PL >> C:\temp\pl.txt
}
##########################-Formatting-##########################################
}
Function GIP
{
# Get the installed printers and write to a file
$GLOBAL:LPrinters | select name | sort name | Out-File C:\temp\LocalPrinters.txt
# Get the contents of the file and load into an array
$GLOBAL:ltext = gc C:\temp\Localprinters.txt
# Remove the old file
remove-item C:\temp\Localprinters.txt
}
Function FIP
{
# Format the data in the array by removing the first four lines and spaces from the array and output to the file
$GLOBAL:ltext[0],$GLOBAL:ltext[3..($GLOBAL:ltext.length-1)] | out-file C:\temp\Localprinters.txt -encoding ascii
# Removes blanks from the file and sets the contents with the new formatting
(gc C:\temp\Localprinters.txt) | ? {$_.trim() -ne "" } | sc C:\temp\Localprinters.txt -encoding ascii
# Removes the blank lines from the end of the file and sets the contents with the new formatting
(gc C:\temp\Localprinters.txt) | ForEach {$_.trimEnd()} | sc C:\temp\Localprinters.txt -encoding ascii
# Assigns the nice neat contents of the file back to the array
$GLOBAL:ltext = (gc C:\temp\Localprinters.txt)
}
Function COMP
{
# Begins comparing the two printer files
# Compares the files and outputs non-built printers to the NewPrinters.txt file
$FinalGroups = Compare-Object (gc C:\temp\pl.txt) (gc C:\temp\LocalPrinters.txt) |
where {$_.SideIndicator -eq "<="} |
select -ExpandProperty inputObject |
sort
#Removes duplicate printers from the list using the -uniq
$FinalGroups | select -uniq | Out-File C:\temp\NewPrinters.txt
$GLOBAL:FinalPrinters = gc C:\temp\NewPrinters.txt
}
Function PNP
{
# Test new printer connectivity
$PingFile = "C:\temp\NewPrinters.txt"
$PingFile
$Pings = gc $PingFile
# Get just the printer names from the New Print File and appends them to a new file called Working
foreach ($p in $Pings)
{
$np = $p.split("\\")
$to = $np[3]
$to >> C:\temp\working.txt
}
# Assigns the contents of the working file (new printers) to an array
$to = gc C:\temp\working.txt
foreach ($t in $to)
{
# Tests the printers connection
write-host "testing printer $t"
$ok = Test-Connection $t -Count 2 -Quiet
#Takes the connectivity results and determines to remove the printer from the file prior to build starting.
if ($ok -ne "TRUE")
{
write-host "$t Communication Failed" -ForegroundColor Red
# If the printer communication fails, the entry is removed from the NewPrinters.txt file
(gc "C:\temp\NewPrinters.txt") -notmatch "$t" | Out-file "C:\temp\NewPrinters.txt"
# Logs the removal of the printer and why it is being removed
#Reinitializes date for more accurate times in the log file
$date11 = Get-Date
$remprt = "$date11 - printer $t will not be built - communication failed"
LogI $remprt
}
else
{
Write-Host "$t Communication Successful" -ForegroundColor Yellow
}
}
}
Function BUILD
{
# Build each new printer found in the NewPrinters text file
$ToBuild = gc C:\temp\NewPrinters.txt
foreach ($item in $ToBuild)
{
# Reinitialized date to get more accurate reporting
$date4 = Get-Date
# Creates Logging info
$LogIt = "Created: $date4 $item"
# Calls function to write information to a log file
LogI $LogIt
Write-Host -NoNewLine "Creating printer $item" -ForegroundColor green
# Printer Build now occurs
(New-Object -ComObject WScript.Network).AddWindowsPrinterConnection("$item")
}
}
##############
####-MAIN-####
##############
# Cleanup High CPU and long running services which interfere with this script
ServiceShutdown
# Cycle the print spooler service
CycleSpooler
# Get Local Printers
GetLocalPrinters
# Archive log maintenance
ArchiveLog
# Starts logging
StartLog
# Get the completed job
`enter code here` $GLOBAL:LPrinters = Receive-Job -job $GLOBAL:job1 -keep | select $props
# Handles Bad pritners in a status of 2 "Printer not Found on Server, unable to connect"
BadPrinter
# Get Network printer list
GNP
# Format Network printer list
FNP
# Get installed/existing printers
GIP
# Format installed/existing printer file
FIP
# Compare existing printers with new printers only keep network printers needing to be built
COMP
# Check if New Printer File is NULL
if ($GLOBAL:FinalPrinters.count -gt 0)
{
# Ping new printer connection and remove printers with failed connections
PNP
# Build new printers
BUILD
}
Else
{
$date6 = Get-Date
LogI "$date6 No new printers were built"
}
# Cleanup temp text files
CleanupTMP
# Stops Logging
EndLog
# Cleanup Archive log files older than the specified ## of days
CleanupLogs
# Restarts Services
ServiceReenable
# Cycle the print spooler service
CycleSpooler
# Printer Maintenance is Complete
Write-Host "Printer Maintenance is complete" -ForegroundColor Red -BackgroundColor Yellow
Example of what is inside the VBS:
Set objNetwork = CreateObject("WScript.Network")
objNetwork.AddWindowsPrinterConnection "\\PrintServer\Printer1"
I do not control nor maintain this file. I am only allowed to run it. This script copies the .vbs to a local text file which is then edited as needed.
Related
Continuing from my previous question:
I have Powershell script that exports Mailboxes Size Results to CSV file.
The Results contain "Total Size" column that display results, and follow by Name.
However, i want the exported CSV file to filter and display only "greater then" 25GB Results, from high to low.
Like that:
Now, there is the traditional way to use excel to filter to Numbers in the CSV results- after the powershell export.
But, i want to have it in the CSV file, so i do not have to do it over and over again.
Here's the script:
Param
(
[Parameter(Mandatory = $false)]
[switch]$MFA,
[switch]$SharedMBOnly,
[switch]$UserMBOnly,
[string]$MBNamesFile,
[string]$UserName,
[string]$Password
)
Function Get_MailboxSize
{
$Stats=Get-MailboxStatistics -Identity $UPN
$IsArchieved=$Stats.IsArchiveMailbox
$ItemCount=$Stats.ItemCount
$TotalItemSize=$Stats.TotalItemSize
$TotalItemSizeinBytes= $TotalItemSize –replace “(.*\()|,| [a-z]*\)”, “”
$TotalSize=$stats.TotalItemSize.value -replace "\(.*",""
$DeletedItemCount=$Stats.DeletedItemCount
$TotalDeletedItemSize=$Stats.TotalDeletedItemSize
#Export result to csv
$Result=#{'Display Name'=$DisplayName;'User Principal Name'=$upn;'Mailbox Type'=$MailboxType;'Primary SMTP Address'=$PrimarySMTPAddress;'IsArchieved'=$IsArchieved;'Item Count'=$ItemCount;'Total Size'=$TotalSize;'Total Size (Bytes)'=$TotalItemSizeinBytes;'Deleted Item Count'=$DeletedItemCount;'Deleted Item Size'=$TotalDeletedItemSize;'Issue Warning Quota'=$IssueWarningQuota;'Prohibit Send Quota'=$ProhibitSendQuota;'Prohibit send Receive Quota'=$ProhibitSendReceiveQuota}
$Results= New-Object PSObject -Property $Result
$Results | Select-Object 'Display Name','User Principal Name','Mailbox Type','Primary SMTP Address','Item Count',#{Name = 'Total Size'; Expression = {($_."Total Size").Split(" ")[0]}},#{Name = 'Unit'; Expression = {($_."Total Size").Split(" ")[1]}},'Total Size (Bytes)','IsArchieved','Deleted Item Count','Deleted Item Size','Issue Warning Quota','Prohibit Send Quota','Prohibit Send Receive Quota' | Export-Csv -Path $ExportCSV -Notype -Append
}
Function main()
{
#Check for EXO v2 module inatallation
$Module = Get-Module ExchangeOnlineManagement -ListAvailable
if($Module.count -eq 0)
{
Write-Host Exchange Online PowerShell V2 module is not available -ForegroundColor yellow
$Confirm= Read-Host Are you sure you want to install module? [Y] Yes [N] No
if($Confirm -match "[yY]")
{
Write-host "Installing Exchange Online PowerShell module"
Install-Module ExchangeOnlineManagement -Repository PSGallery -AllowClobber -Force
}
else
{
Write-Host EXO V2 module is required to connect Exchange Online.Please install module using Install-Module ExchangeOnlineManagement cmdlet.
Exit
}
}
#Connect Exchange Online with MFA
if($MFA.IsPresent)
{
Connect-ExchangeOnline
}
#Authentication using non-MFA
else
{
#Storing credential in script for scheduling purpose/ Passing credential as parameter
if(($UserName -ne "") -and ($Password -ne ""))
{
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword
}
else
{
$Credential=Get-Credential -Credential $null
}
Connect-ExchangeOnline -Credential $Credential
}
#Output file declaration
$ExportCSV=".\MailboxSizeReport_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv"
$Result=""
$Results=#()
$MBCount=0
$PrintedMBCount=0
Write-Host Generating mailbox size report...
#Check for input file
if([string]$MBNamesFile -ne "")
{
#We have an input file, read it into memory
$Mailboxes=#()
$Mailboxes=Import-Csv -Header "MBIdentity" $MBNamesFile
foreach($item in $Mailboxes)
{
$MBDetails=Get-Mailbox -Identity $item.MBIdentity
$UPN=$MBDetails.UserPrincipalName
$MailboxType=$MBDetails.RecipientTypeDetails
$DisplayName=$MBDetails.DisplayName
$PrimarySMTPAddress=$MBDetails.PrimarySMTPAddress
$IssueWarningQuota=$MBDetails.IssueWarningQuota -replace "\(.*",""
$ProhibitSendQuota=$MBDetails.ProhibitSendQuota -replace "\(.*",""
$ProhibitSendReceiveQuota=$MBDetails.ProhibitSendReceiveQuota -replace "\(.*",""
$MBCount++
Write-Progress -Activity "`n Processed mailbox count: $MBCount "`n" Currently Processing: $DisplayName"
Get_MailboxSize
$PrintedMBCount++
}
}
#Get all mailboxes from Office 365
else
{
Get-Mailbox -ResultSize Unlimited | foreach {
$UPN=$_.UserPrincipalName
$Mailboxtype=$_.RecipientTypeDetails
$DisplayName=$_.DisplayName
$PrimarySMTPAddress=$_.PrimarySMTPAddress
$IssueWarningQuota=$_.IssueWarningQuota -replace "\(.*",""
$ProhibitSendQuota=$_.ProhibitSendQuota -replace "\(.*",""
$ProhibitSendReceiveQuota=$_.ProhibitSendReceiveQuota -replace "\(.*",""
$MBCount++
Write-Progress -Activity "`n Processed mailbox count: $MBCount "`n" Currently Processing: $DisplayName"
if($SharedMBOnly.IsPresent -and ($Mailboxtype -ne "SharedMailbox"))
{
return
}
if($UserMBOnly.IsPresent -and ($MailboxType -ne "UserMailbox"))
{
return
}
Get_MailboxSize
$PrintedMBCount++
}
}
#Open output file after execution
If($PrintedMBCount -eq 0)
{
Write-Host No mailbox found
}
else
{
Write-Host `nThe output file contains $PrintedMBCount mailboxes.
if((Test-Path -Path $ExportCSV) -eq "True")
{
Write-Host `nThe Output file available in $ExportCSV -ForegroundColor Green
$Prompt = New-Object -ComObject wscript.shell
$UserInput = $Prompt.popup("Do you want to open output file?",`
0,"Open Output File",4)
If ($UserInput -eq 6)
{
Invoke-Item "$ExportCSV"
}
}
}
#Disconnect Exchange Online session
Disconnect-ExchangeOnline -Confirm:$false | Out-Null
}
. main
How can i achieve that?
It's a bit of an unfortunate scenario, but if you're outputting the results to the file on each iteration, you have 2 options:
At the end of the script, read the output file, filter the >25gb mailboxes, sort the objects, then output again
Instead of writing the user mailbox to the file each user, save to a
variable instead. At the end, filter, sort, then export to file
Without going too far into the code...
Option 1
Might be simplest, as you're working off two input sizes already. After you've retrieved all mailboxes and gotten all statistics, read the exported csv file and filter + sort the data. Then, export the info back to the file, overwriting. Something like
$tempImport = Import-CSV $exportCSV | Where-Object {($_.'Total Size' -ge 25) -and ($_.Unit -eq "GB")} | Sort-Object 'Total Size' -descending
$tempImport | Export-CSV $exportCSV -noTypeInformation
PowerShell may not like overwriting a file read-in on the same command, hence the saving as a temp variable.
Option 2
Create a live variable storing all mailbox data, and write the information at the end of the script instead of opening the file to append data each iteration. Then, at the end of the script, filter and sort before exporting.
$global:largeMailboxes = #() # definition to allow reading through all functions
Then, instead of exporting to CSV each time, add the result to the above variable
$tvar = $null
$tvar = $Results | Select-Object 'Display Name','User Principal Name','Mailbox Type','Primary SMTP Address','Item Count',#{Name = 'Total Size'; Expression = {($_."Total Size").Split(" ")[0]}},#{Name = 'Unit'; Expression = {($_."Total Size").Split(" ")[1]}},'Total Size (Bytes)','IsArchieved','Deleted Item Count','Deleted Item Size','Issue Warning Quota','Prohibit Send Quota','Prohibit Send Receive Quota'
$global:largeMailboxes += $tvar
#
# Alternatively, only add the mailbox if it's larger than 25GB, to avoid adding objects you don't care about
if ($TotalItemSizeinBytes -ge 26843545600) # This is 25 GB, better to make a variable called $minSize or such to store this in, in case you want to change it later.
{
# Above code to add to global variable
}
Once all mailboxes have been added, sort the object
$global:largeMailboxes = $global:largeMailboxes | Sort-Object 'Total Size' -descending
Then export as needed
$global:largeMailboxes | Export-CSV $exportCSV -NoTypeInformation
I have a piece of code in powershell which I use as an "installation recipe".
I use this script to check that the preparation of the PCs is good and that the various software are installed correctly.
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
If ((Test-Path "C:\Program Files\7-Zip") -eq $True)
{Write-Host " ~ 7-ZIP : Installation => OK! ~" -ForegroundColor Green}
else{Write-Host " ~ 7-ZIP : Installation => NOK! ~" -ForegroundColor Red}
Sleep 3
If ((Test-Path "C:\Program Files (x86)\Adobe\Acrobat Reader DC") -eq $True)
{Write-Host " ~ Adobe Reader DC : Install => OK! ~" -ForegroundColor Green}
else{Write-Host " ~ Adobe Reader DC : Install => NOK! ~" -ForegroundColor Red}
exit
If the installation is OK (OK), then it generates a value that we store and then export to a .CSV or .XLSX file. Ditto if the installation is not OK (NOK).
How do you do that?
Thanks for your help
One approach is to save each non-installed software's name into an array for later processing.
That being said, testing paths the way in question could be improved. Instead of typing paths here and there, store them in a collection for easy processing. A hashtable works fine. Like so,
$ht = #{
"7-Zip" = "C:\Program Files\7-Zip"
"Adobe Reader DC" = "C:\Program Files (x86)\Adobe\Acrobat Reader DC"
"FooApp" = "C:\Program Files\FooApp"
}
$failedInstalls = #()
foreach($key in $ht.keys){
if(test-path $ht[$key] ) {
Write-Host " ~ $key : Installation => OK! ~" -ForegroundColor Green
} else {
Write-Host " ~ $key : Installation => NOK! ~" -ForegroundColor Red
$failedInstalls += $key
}
}
$failedInstalls
What's done here is that software names and paths are stored in a hash table. So, one central location for all the paths. Then the collection is iterated and each missing software is added to $failedInstalls array. Changing number of software is trivial, it only requires change in the hash table - no need for if(test-path... statement for each piece of software.
As how to export array as an XSLX or CSV is left as an exercise to the reader.
A Hashtable as shown in VonPryz answer to collect the names and paths of the software to test is indeed the easiest way of dealing with this.
From the questions title,you would like a CSV file with the results of the tests; not only colored console output. For that, you need to loop through the list of software and output objects you collect in a variable like below:
# add as many items here as you would like to test
$software = #{
"7-Zip" = "C:\Program Files\7-Zip"
"Adobe Reader DC" = "C:\Program Files (x86)\Adobe\Acrobat Reader DC"
}
# a template line for output to console
$message = ' ~ {0} : Install => {1}! ~'
# loop through the hashtable Keys
$result = $software.Keys | ForEach-Object {
$installed = Test-Path -Path $software[$_]
if ($installed) { $color = 'Green'; $success = 'OK' }
else { $color = 'Red'; $success = 'NOK' }
# output to console
Write-Host ($message -f $_, $success) -ForegroundColor $color
# output an object to save as CSV
[PsCustomObject]#{
'Software' = $_
'Path' = $software[$_]
'IsInstalled' = $installed
}
}
# output result to console as table
$result | Format-Table -AutoSize
# output result as CSV file
$result | Export-Csv -Path 'D:\Test\InstalledSoftware.csv' -NoTypeInformation
Output of $result on screen as Table:
Software Path IsInstalled
-------- ---- -----------
7-Zip C:\Program Files\7-Zip False
Adobe Reader DC C:\Program Files (x86)\Adobe\Acrobat Reader DC True
I am needing to pull a specific sentence from a log file on multiple remote computers. I have all of the computer names already but I do not know how to go about pulling contents of a file from them and copying all of it to a file so that I can reference the sentence from each computer with its machine name. Basically each machine has a specific number unique to itself that we need.
Before explaining, I assume powershell is the tool to use for this.
There are about 1800 machines and I have a variable for all of those. Then I assume I have to make a loop of some kind that runs on each of those machines.
the loop would pull the text from that file that I need and save it all to a file. I am basically pretty new in my Net Admin position with not a lot of PowerShell experience and I wondered if anyone could help.
$computers = ***list of computers***
$computers | ForEachObject{
Add-Content -Path C:\Users\Public\Activant\Eagle\3log.log -Value "Terminal information for ***need the info that is here***"
}
Get-Content -Path .\TERMINAL NUMBERS.txt
this seems to do what you want. [grin] it builds a scriptblock that does the work, hands that off to Invoke-Command with a list of systems to run it on, gathers the results, creates a list of $Non-Responders, removes unwanted properties added by the I-C cmdlet, and finally shows the two collections.
#requires -RunAsAdministrator
# fake reading in a text file
# in real life, use Get-Content
$ComputerNameList = #'
LocalHost
10.0.0.1
127.0.0.1
BetterNotBeThere
'# -split [System.Environment]::NewLine
$IC_ScriptBlock = {
$TargetFileName = 'C:\Temp\Grouping-Strings-List_2019-07-31.log'
# the " \b\w+\b \b\w+\b " is two words delimited by spaces
# so this will find any line that has two words between the listed phrases
$LinePattern = '^Acid Drum \b\w+\b \b\w+\b Psychedelic$'
# the next line is a no-match patern for testing
#$LinePattern = '^Acid Drum \b\w+\b$'
$Line = (Get-Content -LiteralPath $TargetFileName |
Select-String -Pattern $LinePattern).Line
if ([string]::IsNullOrEmpty($Line))
{
$Line = '__Not Found__'
}
[PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
LineText = $Line
}
}
$IC_Params = #{
ComputerName = $ComputerNameList
ScriptBlock = $IC_ScriptBlock
# comment out the next line to see any errors in the I-C call
ErrorAction = 'SilentlyContinue'
}
$Responders = Invoke-Command #IC_Params
$Non_Responders = $ComputerNameList.Where({$_ -notin $Responders.PSComputerName})
# the next line removes unwated properties added by "Invoke-Command"
$Responders = $Responders |
Select-Object -Property * -ExcludeProperty PSComputerName, PSShowComputerName, RunspaceId
$Responders
'=' * 40
$Non_Responders
output ...
ComputerName LineText
------------ --------
[MySysName] Acid Drum Instrumental Live Psychedelic
[MySysName] Acid Drum Instrumental Live Psychedelic
========================================
10.0.0.1
BetterNotBeThere
if needed, you can create a single collection from the two above fairly directly. [grin]
I think what you are trying to do is to READ the line from a file all computers in your list should have, located at C:\Users\Public\Activant\Eagle\3log.log
In that case, something like below should work:
# use UNC naming for the remote file path
$inputFile = 'C$\Users\Public\Activant\Eagle\3log.log' # I'm guessing this is the file you want to read
$outputFile = 'C:\TERMINAL NUMBERS.txt'
$computers = ***list of computers*** # the array of computer names
$result = $computers | ForEach-Object {
# test if the computer is online
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
# create the full UNC path by prepending the common file path with the computer name
$file = '\\{0}\{1}' -f $_, $inputFile
# test if the file can be found or not
if (Test-Path -LiteralPath $file -PathType Leaf) {
# using non regex string search
$line = (Select-String -LiteralPath $file -Pattern "whatever you're looking for" -SimpleMatch).Line
if (!$line) {
# the file is there, but the pattern was not found
$line = "Pattern not found"
}
}
else {
$line = "File '$inputFile' not found."
}
}
else {
$line = 'Computer is Off-Line'
}
# Finally, add this info to your text file
Add-Content -Path $outputFile -Value "$_ --> $line"
# And emit an object for the $result collection. This will display nicely on screen,
# but also allow for the creation of a CSV file which might be better as output.
[PsCustomObject]#{
'Computer' = $_
'LogInfo' = $line
}
}
Afterwards you can read the output text file or (better I think) use the $result collection for output:
On screen:
$result | Format-Table -AutoSize
To CSV file
$result | Export-Csv -Path 'C:\TERMINAL NUMBERS.csv' -NoTypeInformation -Force
Trying to set up a loop that will run test_netconnection to each line in the text file and then output the response into a different file
I have tried different ways of writing it, just not sure where my loop breaks
$file = 'Z:\servers.txt'
$addresses = Get-Content $file
$reader = New-Object IO.Streamreader $file
while ($reader.ReadLine() -ne $null) { }
foreach ($address in $addresses) {
try {
$Test = Test-NetConnection -ComputerName $addresses -Port xxxx -InformationLevel Quiet
Write-Host "$Test"
}
catch {
Out-File z:/output.txt
}
}
Not getting any output sent to my Out-File, I suspect that my loop is broken
If servers.txt contains the following:
www.google.com
www.microsoft.com
www.stackoverflow.com
...
...then you can loop through it, use Test-NetConnection to see if entries are pingable (ICMP), and export the results as a *.CSV file with the following code:
$servers = Get-Content C:\path\to\servers.txt
$servers | % {
Test-NetConnection $_ | Select ComputerName, PingSucceeded | Export-Csv C:\path\to\results.csv -NoTypeInformation -Append
}
You can extend the exported result properties by adjusting the Select portion of the code.
here's a rather overdone version of the idea. [grin] it uses Tet-Connection instead of the newer Test-NetConnection since win7ps5.1 does not have that cmdlet.
if you nave never seen it used before, the #region comments are code folding markers. that lets one fold away the parts that are not currently of interest.
#region - save default prefs
# the Verbose default is screen display OFF
$AllowVerboseOutput = $True
# the Warning default is screen display ON
$AllowWarningOutput = $True
# save the VerbosePref
$OldVPref = $VerbosePreference
if ($AllowVerboseOutput)
{
# enable screen display of Write-Verbose [it's OFF by default]
$VerbosePreference = 'Continue'
}
# save the WarningPref
$OldWPref = $WarningPreference
if (-not $AllowWarningOutput)
{
# DISABLE Write-Warning screen output [it's ON by default]
$WarningPreference = 'SilentlyContinue'
}
#endregion - save default prefs
# if you want _fewer_ reports, use a larger final time unit
# minutes, hours, whatever suits your needs [*grin*]
$TimeStamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'
$SystemListDir = $env:TEMP
$SystemListFile = 'ServerList.txt'
$FullSystemListFile = Join-Path -Path $SystemListDir -ChildPath $SystemListFile
$ReportDir = $env:TEMP
# if you don't want multiple report files, comment OUT this line & UN-comment the line after it
$ReportFile = -join ('ConnectionRemport', '_-_', $TimeStamp, '.csv')
#$ReportFile = -join ('ConnectionRemport', '.csv')
$FullReportFile = Join-Path -Path $ReportDir -ChildPath $ReportFile
$NoResponse = '-- No Response --'
#region - sample data import
# fake reading in a file
# in real life, use the Get-Content line below
# remove the leading "#" on the next line when you are ready to use a real file
#<#
$SystemList = #'
LocalHost
127.0.0.1
10.0.0.1
'#.Split("`n").Trim()
#>
#endregion - sample data import
# remove the leading "#" on the next line when you are ready to use a real file
#$SystemList = Get-Content -Path $FullSystemListFile
$Results = foreach ($SL_Item in $SystemList)
{
Write-Verbose "Connecting to $SL_Item ..."
if (Test-Connection -ComputerName $SL_Item -Count 1 -Quiet)
{
Write-Verbose " System $SL_Item reached successfully."
$TempObject = [PSCustomObject]#{
MachineName = $SL_Item
Status = 'Online'
# the resulting "sortable" date string is yyyy-MM-ddTHH:mm:ss
TimeStamp = (Get-Date).ToString("s")
}
}
else
{
Write-Warning " Unable to reach $SL_Item."
$TempObject = [PSCustomObject]#{
MachineName = $SL_Item
Status = $NoResponse
# the resulting "sortable" date string is yyyy-MM-ddTHH:mm:ss
TimeStamp = (Get-Date).ToString("s")
}
}
$TempObject
} # end = foreach ($SL_Item in $SystemList)
# display $Results on screen
$Results
# save $Results to CSV file
$Results |
Export-Csv -LiteralPath $FullReportFile -NoTypeInformation
#region - restore default prefs
# restore previuos VerbosePref
$VerbosePreference = $OldVPref
# restore previous WarningPref
$WarningPreference = $OldWPref
#endregion - restore default prefs
on-screen output while running ...
VERBOSE: Connecting to LocalHost ...
VERBOSE: System LocalHost reached successfully.
VERBOSE: Connecting to 127.0.0.1 ...
VERBOSE: System 127.0.0.1 reached successfully.
VERBOSE: Connecting to 10.0.0.1 ...
WARNING: Unable to reach 10.0.0.1.
final onscreen output from the $Results collection ...
MachineName Status TimeStamp
----------- ------ ---------
LocalHost Online 2019-08-02T12:16:43
127.0.0.1 Online 2019-08-02T12:16:43
10.0.0.1 -- No Response -- 2019-08-02T12:16:47
CSV file content ...
"MachineName","Status","TimeStamp"
"LocalHost","Online","2019-08-02T12:16:43"
"127.0.0.1","Online","2019-08-02T12:16:43"
"10.0.0.1","-- No Response --","2019-08-02T12:16:47"
After getting help in Print PDF to XPS using Powershell I made two scripts: one converts a PDF to XPS and the other XPS to PDF using a workflow. When combining them into one script, the first workflow converts the file to XPS, however, the second workflow doesn't seem to work at the appropriate time or at all.
There are some cleanup items at the end of the second workflow that do seem to be working properly. Can I delay the start of the second workflow somehow to start at the appropriate time?
# Define the directories used for converting files
$secure_pdf_dir = Select-FolderDialog # the variable contains user folder selection
$xps_dir = "aaa"
$unsecure_pdf_dir = "bbb"
$secure_pdf_archive = "ccc"
$xps_archive = "ddd"
$unsecure_pdf_archive = "eee"
$date = $(get-date -f yyyy-MMM-dd)
# Archives old files except secure pdfs
# Archives old xps files
New-Item $xps_archive\$date -type Directory
copy-Item $xps_dir\* -Recurse $xps_archive\$date -force
Remove-Item $xps_dir\*
# Archives old unsecure pdf files
New-Item $unsecure_pdf_archive\$date -type Directory
copy-Item $unsecure_pdf_dir\* -Recurse $unsecure_pdf_archive\$date -force
Remove-Item $unsecure_pdf_dir\*
# Converts PDF to XPS
function print_files($secure_pdf_dir){
#The purpose of this counter is to number your .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $_.FullName
}
}
# The following function keeps checking for a new window called "Save Print Output As". When the window shows up, it enters the name of the file and press ENTER.
function enter_my_names($fullname){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys("$basename")
$wshell.SendKeys("{ENTER}")
}
# The following function launches simultaneously a print job on the input file and a function waiting for the print job to show up to name the file.
workflow same_time{
Param(
$fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names($fullname)
}
}
# MAIN PROGRAM
# Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
# Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
# Starts the main job
print_files($secure_pdf_dir)
# Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
# This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 5
# Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
Move-Item $secure_pdf_dir\*.oxps $xps_dir
# Converts XPS to PDF
function print_xps_files($xps_dir){
#The purpose of this counter is to number your .xps files
Get-ChildItem $xps_dir -Filter *.oxps -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $_.FullName
}
}
# The following function keeps checking for a new window called "Save Print Output As". When the window shows up, it enters the name of the file and press ENTER.
function enter_my_xps_names($fullname){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys("$basename")
$wshell.SendKeys("{ENTER}")
}
function press_enter($enter){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Print') -ne $true){
$wshell.AppActivate('Print')
}
$wshell.SendKeys("{ENTER}")
}
# The following function launches simultaneously a print job on the input file and a function waiting for the print job to show up to name the file.
workflow same_time2{
Param(
$fullname
)
sequence{
Start-Process -FilePath $fullname –Verb Print -PassThru
press_enter($enter)
enter_my_xps_names($fullname)
}
}
# MAIN PROGRAM
# Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
# Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
# Sets the XPS printer as Default
$printer.SetDefaultPrinter()
# Starts the main job
print_files($xps_dir)
# Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
# This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 5
# Archives old secure pdfs
# Archives old secure pdf files
New-Item $secure_pdf_archive\$date -type Directory
copy-Item $secure_pdf_dir\* -Recurse $secure_pdf_archive\$date -force
Remove-Item $secure_pdf_dir\*