PowerShell Script to query and delete print jobs older than "x" days - powershell

I started putting this PowerShell Script together, the hope would be to replace some tasks that are currently carried out manually
I'm using the
get-Date.AddDays()
function
I'm using the ISE to build the script and in testing I get output if I single out the 'starttime' property, but this seems to be a catch all because the values all come up null, ideally I'd like to use the 'timesubmitted' property, but the date seems to output in an odd that I don't think is being read correctly because my queries with 'timesubmitted' always come up empty
it comes out in this format, if you do an open query
20120416030836.778000-420
here's what I have so far.
disregard the | 'format-table' function that's just so I can see if I'm getting the desired output
#Clears Old Print Jobs on Specified server
#Sets Execution Policy for Script to run
Set-ExecutionPolicy RemoteSigned -Force
#establishes variable for cutoff date
$d = Get-Date
$old = $d.AddDays(-4)
#Queries WMI and retrieves print jobs
Get-WmiObject -class win32_printjob -namespace "root\CIMV2" | where-object {$_.timesubmitted -lt
"$old"} | ft caption,document,jobid,jobstatus,owner,timesubmitted

In PowerShell every WMI instance has a ScriptMethod that you can use to convert the dates from WMI format to .NET format:
Get-WmiObject Win32_PrintJob |
Where-Object { $_.ConvertToDateTime($_.TimeSubmitted) -lt $old } |
Foreach-Object { $_.Delete() }

Just an update in case anyone is looking in 2021.
This command/syntax worked for me in 2008 R2 (PowerShell version 2.0) (I was able to piece this together from this page, as well as others).
Finds all jobs over 30 minutes and deletes them:
Get-wmiobject win32_printjob
| Where-Object {[System.Management.ManagementDateTimeConverter]::ToDateTime($_.TimeSubmitted) -lt (Get-Date).addminutes(-30)}
| ForEach-Object { $_.delete() }*

Related

get-eventlog returns different results each run

I have a function that runs the directory sync tool (for azure AD) on a remote server.
the problem starts with the cmdlet that suppose to return the time the sync started.
It first runs Start-ADSyncSyncCycle -PolicyType Delta on the remote server (using Invoke-Command). After that it runs this code to get the newest event (using specific parameters):
$Event = Get-EventLog -LogName Application -ComputerName $ComputerName -Newest 1000 | Where-Object {$_.Source -eq "Directory Synchronization" -and $_.Message -match "Scheduler::StartSyncCycle : Started sync cycle."} |Sort-Object Time |Select-Object -First 1
The problem starts with the if statement that follows
if ($Event) {Write-Output "Started sync cycle at" $Event.TimeGenerated} else {Write-Output "Sync did not start"}
The result of that if is "Started sync cycle at " (with an empty space after the "at") meaning it cant grab the $Event.TimeGenerated. When doing Write-Host $Event it shows System.Diagnostics.EventLogEntry and the weird thing is that other times the result of the if statement shows the correct info like "Started sync cycle at Wednesday, February 17, 2021 3:27:16 PM".
Can anyone please help me figure this out? What can cause it to show a different result on each run?
Or better yet, what is this System.Diagnostics.EventLogEntry object it sometimes returns?
I have a feeling Im missing something dumb... :(
Thanks in advance.
I see a couple bugs. The core one is here:
Sort-Object Time | Select-Object -First 1
First, while the output for Get-EvenLog shows a column named Time, there is no property actually named Time. You can see this with Get-EventLog -LogName Application -Newest 1 | Format-List -Property *. There are two properties you might want: TimeGenerated and TimeWritten. Offhand, I'm not sure which Time represents since the events I see have the same value for each.
Second, the default order for sorting a datetime is ascending. That means this code gets the oldest event in the newest 1000 events. That's not what you described the code doing. You should use the -Descending switch to get the newest log record.
Next, you should specify the source when you call Get-EventLog with the -Source parameter since you know the exact source. That will improve performance by making the command do the filtering for you.
Finally, your pattern matching here contains special regex characters:
$_.Message -match "Scheduler::StartSyncCycle : Started sync cycle."
If this is a literal string you want to match, you should instead match against:
$_.Message -match "Scheduler::StartSyncCycle : Started sync cycle\."
So I would write your code like so:
$Event = Get-EventLog -LogName Application -ComputerName $ComputerName -Source "Directory Synchronization" -Newest 1000 |
Where-Object Message -match "Scheduler::StartSyncCycle : Started sync cycle\." |
Sort-Object -Property TimeGenerated -Descending |
Select-Object -First 1
I'm not quite sure what the issue is with the Write-Output statement because I can't recreate it here. However, I would try your code like so:
if ($null -ne $Event) {
"Started sync at $($Event.TimeGenerated)"
}
else {
"Sync did not start."
}

Powershell - check for last time computers were restarted

I need to make sure all computers are being restarted at least once a week, and I've been googling on it, and I found something, but it's... rather something simple, something... 1-timer... What I want is to make it a bit more advanced.
Here's the command I found online:
Get-WmiObject Win32_OperatingSystem -ComputerName <computername> : select cname, #(LABEL='LastBootUpTime' ;EXPRESSION=($_.ConverttoDateTime($_.lastbootuptime)))
(I'm not sure if sharing the links is a good idea here, but I just googled for "check when remote computer was last restarted" and clicked on a link on enterprisedaddy)
I do not know anything about PowerShell, so I'm asking for your help. Also, it may be useful not only to me, but to others as well...
Here's what I want:
Create the *.ps1 file (I can do that) and make it run it 24/7
Instead of copying all computers each time, I want to append new computers to the list, because otherwise, the list would be very, very long... and I don't want to be deleting a whole bunch of computers and making sure only 1 copy is left and deleting all other every day...
Export the list into either *.txt or (even better) *.csv
At the end of the day, the *.ps1 would create a new *.txt or *.csv file and start it all over. In a new file. Of course leaving the old one (or all others, starting day 2...) for the review...
I understand there's very little hope, but if you can help me - great. If not - well, it may take forever to google for it all, but in the end, I may be able to find it all myself... Although there is even less hope here...
To be fair, all I need is to see the last time the computers were restarted, and that's all, so if you know of an easier way - I'm ready to read about other ideas.
This is not a full answer, but should help you toward your goal. If I'm interpreting your question correctly you want to check the last boot up time daily and report machines that haven't rebooted in the last 7 days. That data should then be stored in a csv file.
So this is actually quite easy to do, and PowerShell is a great place to do it. However, you have to "develop" on top of what you've already discovered. Set goals and meet those goals.
A start might be something like:
$Today = Get-Date
$ReportDate = $Today.AddDays( -7 )
$OutputCsv = ("C:\Temp\" + $Today.ToString( "yyyy-MM-dd" ) + ".csv" )
$BootTimes =
Get-WmiObject Win32_OperatingSystem -ComputerName pyex06 |
Select-Object PSComputerName, #{ LABEL='LastBootUpTime' ;EXPRESSION={ $_.ConverttoDateTime($_.lastbootuptime) } }
$BootTime |
Where-Object{ $_.LastBootUpTime -lt $ReportDate } |
Export-Csv -Path $OutputCsv -NoTypeInformation
Note: I didn't test this just hammered it out quickly for demonstration.
What's happening:
Get the current date.
Use the current date to calculate the date boundry earlier than which you want to report on.
Use the data again to derive a string suitable for naming a somewhat unique output file.
Do the query, add the LastBootUpTime property. Note: I made several syntax corrections there.
Now run the results through a Where{} statement that will filter for boot times more than 7 days old, and export to a CSV file.
Again this is just a start. You will need to add an ability to work against more than one computer, You'll need to think about what your input is going to be for that. You will probably also want to add error handling as WMI (and later CIM) connections can and do fail.
An aside: Get-WMIObject is deprecated. It's been replaced by Get-CimInstance. GetCimInstance will return a LastBootUpTime property as an actual date, with no need to add the property. That command would look something like?
Get-CimInstance Win32_OperatingSystem -ComputerName ComputerName |
Select-Object PSComputerName,LastBootUpTime
All of these things are very well documented. In fact I'm sure somebody has even published a script doing exactly what you want. You have to have some idea of how building a larger script will progress and hopefully I've given you that much. It's a series of solving individual issues and putting those solutions together etc.
BTW, the WMI stuff is depreciated, and CIM is the new hotness going forward. Note that win using cross-platform PowerShell (PowerShell Core), the WMI cmdlets do not exist.
$PSVersionTable.PSVersion
# Results
<#
Major Minor Build Revision
----- ----- ----- --------
5 1 19041 1
#>
(Get-Command -Name '*wmi*').Count
# Results
<#
21
#>
$PSVersionTable.PSVersion
# Results
<#
Major Minor Patch PreReleaseLabel BuildLabel
----- ----- ----- --------------- ----------
7 0 3
#>
(Get-Command -Name '*wmi*').Count
# Results
<#
0
#>
Secondly, the command you posted is not syntactically correct and thus would never work.
Lastly, always do the basic stuff first to make sure you are getting what you'd expect before taking the next step.
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -Property '*'
# Results
<#
...
CSName : Lab01
...
Description :
InstallDate : 20-Jun-20 18:59:14
...
Distributed : False
LastBootUpTime : 13-Aug-20 01:00:42
LocalDateTime : 20-Aug-20 14:01:53
...
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
#>
Note, you can get the dates directly with no conversions needed, then format as needed.
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -Property CSName, LastBootUpTime,
#{
Name = 'TimeSpanSinceLastRestart'
Expression = {New-TimeSpan -Start $(Get-Date) -End $PSItem.LastBootUpTime }
}
# Results
<#
CSName LastBootUpTime TimeSpanSinceLastRestart
------ -------------- ------------------------
Lab01 13-Aug-20 01:00:45 -7.15:02:24.1130902
#>
As for this...
My question is how to make it run 24/7 and append new computer
... just put in in a scheduled task (on each host or on an admin workstation where you get computer names from AD or from a file) and export to either a CSV or a file using the -Append parameter.
(Get-ADComputer).Name |
ForEach{
Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $PSItem |
Select-Object -Property CSName, LastBootUpTime,
#{
Name = 'TimeSpanSinceLastRestart'
Expression = {New-TimeSpan -Start $(Get-Date) -End $PSItem.LastBootUpTime }
} |
Export-Csv -Path 'D:\Temp\SystemRebootReport.csv' -Append -NoTypeInformation -WhatIf
}
# Results
<#
What if: Performing the operation "Export-Csv" on target "D:\Temp\SystemRebootReport.csv".
#>
Just remove the -WhatIf to create the file.
Update
As for ...
[So how do I use this implicit remoting instead then?]
...this is all detailed in the Powershell Help files, see
about_Remote_Requirements - PowerShell | Microsoft Docs
'PowerShell implicit remoting active directory'
https://www.youtube.com/results?search_query=powershell+implicit+remoting+active+directory
https://www.itprotoday.com/powershell/powershell-implicit-remoting-never-install-module-again
TechNet Install RSAT for Windows 10 1809 and 1903 and 1909 -
automated

Printer Migration - Powershell script

I have found some great examples on foreach loops in Powershell here but I just can't wrap my head around foreach loops for what I am doing.
I found great scripts that deal with migrating printer when migrating from one Windows print server to another however my challenge is that I am migrating from an Novell iPrint server to a Windows server.
The struggle is that the printer name or share name (or any printer property) for iPrint printer is not the hostname so I have to come up with some translation table with iPrint name and Printer hostname.
Initially, I wanted to just have column 2 of my translation table have it execute my powershell command to install a network printer which would make things easier.
I am in the process of trying to create a logon script to query printers that are installed on computer and have it do a 'foreach' loop against a CSV with iPrint names and hostnames.
csv 1
installediprintprintername1
installediprintprintername2
installediprintprintername3
printtranslationtable.csv
column 1 column 2
iprintprintername1 hostnameprinter1
iprintprintername2 hostnameprinter2
iprintprintername3 hostnameprinter3
iprintprintername4 hostnameprinter4
This is what I got so far but not able to get it to work. Any help would be appreciated!
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
foreach ($iprintprinter in $printtranslationtable) {
foreach ($name in $csv1) {
if ($name -eq $printtranslationtable.column1) {
Write-Host $newPrinter = $printtranslationtable.column2
}
}
}
Update
So I was able to tweak the script #TheMadTechnician suggested and able to get this PS script to work in my environment. What I am trying to do is to check if new printers are installed and if they are then just exit script. This is what I have but can't get it to exit or break. I was also trying to write the new printers into text file but not necessary, I would like for it to stop executing script.
if (($printers.name -like "\winprint*") -eq $true) {
$printers.name -like "\winprint\" | out-file -FilePath "C:\windowsprinters.txt" -Append
{break} {exit}
}
When you read the file with Import-Csv, PowerShell creates an array of custom objects with property names from the header line. On the other hand Get-Content produces simple array of string values. I came up with this one liner, which goes thru the translation table and checks if the printer list contains one. This is not optimal if you have billions of printers, but keeps things clear:
printers.txt:
iprinter2
iprinter3
printertable.csv:
"Column1";"Column2"
"iprinter1";"hostname1"
"iprinter2";"hostname2"
"iprinter3";"hostname3"
"iprinter4";"hostname4"
PowerShell:
$printers = Get-Content .\printers.txt
$prtable = Import-Csv -Delimiter ";" .\printertable.csv
$prtable | ?{ $printers -contains $_.Column1 } | %{Write-Host "Install $($_.Column2)"}
Ok, so you query what printers are installed, and you have a translation table loaded from a CSV, now you just need to look at that translation table and cross reference which entries have a listing in the local computer's printer listings.
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
$printertranslation | Where{$_.Column1 -in $printers.ShareName} | ForEach{ Add-Printer $_.Column2 }
I don't know what property of the win32_printer object aligns best for you, but I would suggest ShareName or DeviceId. Those should be something like:
ShareName: XeroxColor02
DeviceId: \\printserver\XeroxColor02

test-connection that supports wildcard? workaround?

trying to see if anyone has a known workaround for using the test-connection cmdlet in powershell to ping wildcard entries in DNS.
I'm trying to clean out our DNS db and exported a list from our BIND server and am in the process of just pinging through the 600+ machines to see if anything responds. I made my own simple script but have also found one that works slightly better on this forum. The script works but the cmdlet help files state that the -computername parameter does not support wildcards and sure enough, when i run the script all CNAME records are reporting down/false when they actually should be responding. The code I'm using is below and is kind of messy but I just needed something quick and it works, but I've included it below for reference:
Get-Content -path C:\Work\testy.txt | ForEach-Object { Test-Connection -ComputerName $_ -Count 1 -AsJob } | Get-Job | Receive-Job -Wait | Select-Object #{Name='ComputerName';Expression={$_.Address}},#{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { $true } else { $false }}} |out-file -FilePath c:\work\TEST.txt
As pointed out by briantist, any non-existing record name will do. You could generate a GUID to substitute the * in your record name:
"subdomain.domain.tld","*.domain.tld" |ForEach-Object {
Test-Connection -ComputerName $($_ -replace '\*',"$([guid]::NewGuid())")
}
Your expression for whether it's "Reachable" or not can be simplified as well:
#{Name='Reachable'; Expression={[bool]($_.StatusCode -eq 0)}}

PowerShell 3.0 - Setting Affinity to CPU per USER's PROCESS

my first post here. I am working on a script using powerShell, the objective is to set a certain amount of CPU-threads per USER's process, using the forum here, i was able to find most of the answers, and even got my script to run, except, if it sets the affinity, it sets it to EVERY-Process, not just the user i need.
here is the code(with comments):
# GET LIST of all process running
$pList = get-wmiobject win32_process
# loop through created array and get the OWNER of the processes
foreach ($p in $pList) {
#If "myUserName" is found:
if ($p.getowner().User -eq 'myUserName') {
# get process name
$procName = $p.ProcessName
# trim STRING to remove EXE
$procName = $procName.Replace('.exe','')
# use get-process to make array of processes run by "myUserName"
$activeProc = Get-Process -name $procName
# Loop to set affinity for each process
foreach ($i in $activeProc){
$i.ProcessorAffinity=0xFE
}
}
}
when i execute this command, all of the process are set to new Thread Count,
any suggestions how to make it ONLY adjust threads for SPECIFIC user?
Thanks a lot guys!
this is pretty urgent.
By calling get-process -name $procName you are finding all processes that have the same name as one run by the user.
Instead of using the ProcessName, use ProcessId.
In PowerShell version 4.0, you can use the -IncludeUserName parameter on the Get-Process cmdlet. Once you have a list of processes, you can then filter then using the Where-Object cmdlet, which has a default alias of ?.
Get-Process -IncludeUserName | Where-Object -FilterScript { $PSItem.UserName -match 'system' };
Or short-hand might look like this:
gps -inc | ? { $_.UserName -match 'system' };
Note: Using the -IncludeUserName parameter requires privilege elevation.