Display results of a SQL query with PowerShell - powershell

I have been provided a PS script which calls a simple SQL query, displays the results and plays a voice... supposedly. This is designed to go into a BI gadget which just displays a number from the PS script.
The correct count from the query is not displaying and is for some reason stuck on 2.
$sql = "<simple COUNT query>"
$items = invoke-sql -server "<server name>" -database "<db name>" -user "<uname>" -password "<password>" -sql $sql -timeout 180
$day = (get-date).DayOfWeek.Value__
# $items[0] = 10
if ($day -ge 1 -and $day -le 5)
{
$date = Get-Date
if ($date.Hour -ge 8 -and $date.Hour -lt 17)
{
if ($items[0] -gt 0)
{
$voice = New-Object -Com sapi.spvoice
$voice.Rate = 1
$voice.volume = 100
Do {
Start-Sleep -s 1
$Counter = Get-Date
}
Until ($Counter.Second/15 -is [int])
if ($items[0] -gt 1)
{
[void]$voice.Speak("New Ticket.")
}
else
{
[void]$voice.Speak("New Ticket.")
}
}
}
}
Write-Output $items.Count
The SQL component I have no issues with and definitely works correctly when run, but I have no experience with PowerShell scripting so can someone please assist?

$Items.count is going to give you the number of records in your result set. Technically these are DataRow objects and $Items is a DataTable. If you are getting multiple rows back, you need to determine WHICH row you want to show.
Basically the way to would query the returned DataTable is:
($Items[x]).Column
Where x is the 0-indexed row you want to return and Column is the name of the column you want to return. So if you want the ItemCount column from the second row you would use:
($Items[1]).ItemCount

Related

Powershell studio datagridview if statement

I have a cellpainting event and am trying to correct/clean this IF statement up. I think I'm getting lost in my parenthess. Is someone able to take a second look at this? Thanks for your time.
My end goal is the IF statemant to be: Column 1 date older than 42 days or not $null and column 4 value = "SEP"
$datagridview1_CellPainting=[System.Windows.Forms.DataGridViewCellPaintingEventHandler]{
$SEPreturnlimit = "{0:MM/dd/yyyy}" -f (get-date).AddDays(-42)
if ((($_.ColumnIndex -eq 1 -and ([datetime]$_.Value -le $SEPreturnlimit -and [datetime]$_.Value.ToString() -ne $null))) -and ($datagridview1.rows .Cells[4].Value -eq "SEP")) #Column 1 date older than 42 days or not $null **and** column 4 value = "SEP"
{
$this.Rows[$_.RowIndex] | %{ $_.DefaultCellStyle.BackColor = 'crimson' } #Color Row
}
I would split the if conditions into separate nested ifs and also add a try..catch
$datagridview1_CellPainting = [System.Windows.Forms.DataGridViewCellPaintingEventHandler] {
if ($_.ColumnIndex -eq 1 -and $datagridview1.rows.Cells[4].Value -eq 'SEP') {
$SEPreturnlimit = (Get-Date).AddDays(-42).Date # set to 42 days back at midnight
try {
# if we succeed in parsing out a datetime object
$date = [datetime]$_.Value
# test if we have a DateTime object and if that date is older than the reference date
if (($date) -and $date-le $SEPreturnlimit) {
# cannot check this myself, but shouldn't that simply be
# $this.Rows[$_.RowIndex].DefaultCellStyle.BackColor = 'crimson'
$this.Rows[$_.RowIndex] | ForEach-Object{ $_.DefaultCellStyle.BackColor = 'crimson' } #Color Row
}
}
catch { <# do nothing #> }
}
}
I think I fixed the placement, but check the logic. Have a look at it this way
if
(
(
$_.ColumnIndex -eq 1 -and
(
[datetime]$_.Value -le
$SEPreturnlimit -and
[datetime]$_.Value.ToString() -ne
$null
)
) -and
(
$datagridview1.rows .Cells[4].Value -eq
'SEP'
)
) #Column 1 date older than 42 days or not $null **and** column 4 value = "SEP"

powershell: if loop, while the first position of arraylist in in process

I have an arraylist, in which I am going to save some values. I am doing a foreach loop and if a value is going to be saved at the first position of the ArrayList, I want to output a "First position" line and otherwise, nothing should be done.
The if block that I wrote below, doesn't work.
<# Don't consider this code from here
[System.Collections.ArrayList]$alist = #()
$al ="keeranpc01,www.google.ch,192.168.0.25"
$al = $al.Split(",")
foreach($h in $al){
# Abstand vor dem Hostnamen löschen
if($h.contains(' ')) {
$h = $h -replace '\s', ''
}
$alist.Add($h)
}
to here
#>
#### Start here
[System.Collections.ArrayList]$rear = #()
foreach($ha in $alist) {
$PingConnetion = Test-Connection $ha -Count 1 -ErrorAction SilentlyContinue
$pcre = $ha
$pire = if($PingConnetion.ResponseTime -ne $null) {Write-output 'Erreichbar'}else{Write-Output 'Nicht Erreichbar'}
$zure = $PingConnetion.ResponseTime
$zeit = Get-Date -Format HH:mm:ss
if($alist[$_] -eq $alist[0]) {Write-Host 'First position'}
[void]$rear.Add([PSCustomObject]#{Zeit = $zeit; Host = $pcre; IPv4 = $PingConnetion.IPV4Address.IPAddressToString; Ping = $pire; Zugriffszeit = $zure; })
}
how should I write the if statement so that it is possible? I expect if-statement to work, Only when the first position of ArrayList is in process
thx
What you are tying to do does work except you are tying to compare element zero of your alist to a ordinal position of your alist which is invalid. You would need to compare the following:
if($ha -eq $alist[0]) {Write-Host 'First position'}
Below is a worked example that might be clearer.
$input = 1..10
foreach($x in $input){
if($input[0] -eq $x){
write-host "First Position"
}
$x
}

Extract Pages from a PDF using itextsharp in Powershell

I have been researching this for weeks now and can't seem to make much ground on the subject. I have a large PDF (900+ pages), that is the result of a mail merge. The result is 900+ copies of the same document which is one page, with the only difference being someone's name on the bottom. What I am trying to do, is have a powershell script read the document using itextsharp and save pages that contain a specific string (the person's name) into their respective folder.
This is what I have managed so far.
Add-Type -Path C:\scripts\itextsharp.dll
$reader = New-Object iTextSharp.text.pdf.pdfreader -ArgumentList
"$pwd\downloads\TMs.pdf"
for($page = 1; $page -le $reader.NumberOfPages; $page++) {
$pageText = [iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader,$page).Split([char]0x000A)
if($PageText -match 'DAN KAGAN'){
Write-Host "DAN FOUND"
}
}
As you can see I am only using one name for now for testing. The script finds the name properly 10 times. What I cannot seem to find any information on, is how to extract pages that this string appears on.
I hope this was clear. If I can be of any help, please let me know.
Thanks!
I actually just finished writing a very similar script. With my script, I need to scan a PDF of report cards, find a student's name and ID number, and then extract that page and name it appropriately. However, each report card can span multiple pages.
It looks like you're using iTextSharp 5, which is good because so am I. iTextSharp 7's syntax is wildly different and I haven't learned it yet.
Here's the logic that does the page extraction, roughly:
$Document = [iTextSharp.text.Document]::new($PdfReader.GetPageSizeWithRotation($StartPage))
$TargetMemoryStream = [System.IO.MemoryStream]::new()
$PdfCopy = [iTextSharp.text.pdf.PdfSmartCopy]::new($Document, $TargetMemoryStream)
$Document.Open()
foreach ($Page in $StartPage..$EndPage) {
$PdfCopy.AddPage($PdfCopy.GetImportedPage($PdfReader, $Page));
}
$Document.Close()
$NewFileName = 'Elementary Student Record - {0}.pdf' -f $Current.Student_Id
$NewFileFullName = [System.IO.Path]::Combine($OutputFolder, $NewFileName)
[System.IO.File]::WriteAllBytes($NewFileFullName, $TargetMemoryStream.ToArray())
Here is the complete working script. I've removed as little as possible to provide you a near working example:
Import-Module -Name SqlServer -Cmdlet Invoke-Sqlcmd
Add-Type -Path 'C:\...\itextsharp.dll'
# Get table of valid student IDs
$ServerInstance = '...'
$Database = '...'
$Query = #'
select student_id, student_name from student
'#
$ValidStudents = #{}
Invoke-Sqlcmd -Query $Query -ServerInstance $ServerInstance -Database $Database -OutputAs DataRows | ForEach-Object {
[void]$ValidStudents.Add($_.student_id.trim(), $_.student_name)
}
$PdfFiles = Get-ChildItem "G:\....\*.pdf" -File |
Select-Object -ExpandProperty FullName
$OutputFolder = 'G:\...'
$StudentIDSearchPattern = '(?mn)^(?<Student_Id>\d{6,7}) - (?<Student_Name>.*)$'
foreach ($PdfFile in $PdfFiles) {
$PdfReader = [iTextSharp.text.pdf.PdfReader]::new($PdfFile)
$StudentStack = [System.Collections.Stack]::new()
# Map out the PDF file.
foreach ($Page in 1..($PdfReader.NumberOfPages)) {
[iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($PdfReader, $Page) |
Where-Object { $_ -match $StudentIDSearchPattern } |
ForEach-Object {
$StudentStack.Push([PSCustomObject]#{
Student_Id = $Matches['Student_Id']
Student_Name = $Matches['Student_Name']
StartPage = $Page
IsValid = $ValidStudents.ContainsKey($Matches['Student_Id'])
})
}
}
# Extract the pages and save the files
$LastPage = $PdfReader.NumberOfPages
while ($StudentStack.Count -gt 0) {
$Current = $StudentStack.Pop()
$StartPage = $Current.StartPage
$EndPage = $LastPage
$Document = [iTextSharp.text.Document]::new($PdfReader.GetPageSizeWithRotation($StartPage))
$TargetMemoryStream = [System.IO.MemoryStream]::new()
$PdfCopy = [iTextSharp.text.pdf.PdfSmartCopy]::new($Document, $TargetMemoryStream)
$Document.Open()
foreach ($Page in $StartPage..$EndPage) {
$PdfCopy.AddPage($PdfCopy.GetImportedPage($PdfReader, $Page));
}
$Document.Close()
$NewFileName = 'Elementary Student Record - {0}.pdf' -f $Current.Student_Id
$NewFileFullName = [System.IO.Path]::Combine($OutputFolder, $NewFileName)
[System.IO.File]::WriteAllBytes($NewFileFullName, $TargetMemoryStream.ToArray())
$LastPage = $Current.StartPage - 1
}
}
In my test environment this processes about 500 students across 5 source PDFs in about 15 seconds.
I tend to use constructors instead of New-Object, but there's no real difference between them. I just find them easier to read.

Powershell script exits ForEach-Object loop prematurely [duplicate]

This question already has answers here:
Why does 'continue' behave like 'break' in a Foreach-Object?
(4 answers)
Closed 5 years ago.
So I've been writing a script that will take all of the data that is stored in 238 spreadsheets and copy it into a master sheet, as well as 9 high level report sheets. I'm really not sure why, but after a specific document, the script ends prematurely without any errors being posted. It's very strange. I'll post some anonymized code below so maybe someone can help me find the error of my ways here.
As far as I can tell, the document that it exits after is fine. I don't see any data errors in it, and the info is copied successfully to the master document before powershell just calls it quits on the script completely.
I've tried changing the size of the data set by limiting only to the folder that contains the problem file. It still ends after the same file with no error output. I cannot upload the file due to company policy, but I really don't see anything different about the data on that one file when compared to any other file of the same nature.
Also, apologies in advance for the crappy code. I'm not a developer and have been relearning powershell since it's the only tool available to me right now.
$StartTime = Get-Date -Format g
Write-Host $StartTime
pushd "Z:\Shared Documents\IO"
$TrackTemplate = "C:\Users\USERNAME\Desktop\IODATA\MasterTemplate.xlsx"
# Initialize the Master Spreadsheet
$xlMaster = New-Object -ComObject Excel.Application
$xlMaster.Visible = $False
$xlMaster.DisplayAlerts = $False
$MasterFilePath = "C:\Users\USERNAME\Desktop\IODATA\Master.xlsx"
Copy-Item $TrackTemplate $MasterFilePath
$wbMaster = $xlMaster.Workbooks.Open($MasterFilePath)
$wsMaster = $wbMaster.Worksheets.Item(2)
$wsMaster.Unprotect("PASSWORD")
$wsMasterRow = 3
# Initialize L4 Document Object
$xlL4 = New-Object -ComObject Excel.Application
$xlL4.Visible = $False
$xlL4.DisplayAlerts = $False
# Initialize object for input documents
$xlInput = New-Object -ComObject Excel.Application
$xlInput.Visible = $False
$xlInput.DisplayAlerts = $False
# Arrays used to create folder path names
$ArrayRoot = #("FOLDER1","FOLDER2","FOLDER3","FOLDER4","FOLDER5","FOLDER6","FOLDER7","FOLDER8","FOLDER9")
$ArrayShort = #("SUB1","SUB2","SUB3","SUB4","SUB5","SUB6","SUB7","SUB8","SUB9")
# $counter is used to iterate inside the loop over the short name array.
$counter = 0
$FileNumber = 0
$TotalFiles = 238
$ArrayRoot | ForEach-Object {
$FilePathL4 = "C:\Users\USERNAME\Desktop\IODATA\ROLLUP\" + $ArrayShort[$counter] + "_DOC_ROLLUP.xlsx"
Copy-Item $TrackTemplate $FilePathL4
$wbL4 = $xlL4.Workbooks.Open($FilePathL4)
$wsL4 = $wbL4.Worksheets.Item(2)
$wsL4.Unprotect("PASSWORD")
$wsL4Row = 3
If ($ArrayShort[$counter] -eq "SUB7") {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\" + $ArrayShort[$counter] + " - DOC v2\"}
Else {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\!" + $ArrayShort[$counter] + " - DOC v2\"}
Get-ChildItem -Path $FilePath | ForEach-Object {
If ($_.Name -eq "SPECIFIC_DOC.xlsx") {Continue}
$FileNumber += 1
Write-Host "$FileNumber / $TotalFiles $_"
$wbInput = $xlInput.Workbooks.Open($_.FullName)
$wsInput = $wbInput.Worksheets.Item(2)
$wsInputLastRow = 0
#Find the last row in the Input document
For ($i = 3; $i -le 10000; $i++) {
If ([string]::IsNullOrEmpty($wsInput.Cells.Item($i,1).Value2)) {
$wsInputLastRow = $i - 1
Break
}
Else { Continue }
}
[void]$wsInput.Range("A3:AC$wsInputLastRow").Copy()
Start-Sleep -Seconds 1
[void]$wsMaster.Range("A$wsMasterRow").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsMasterRow += $wsInputLastRow - 2
[void]$wsL4.Range("A$wsL4Row").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsL4Row += $wsInputLastRow - 2
$wbInput.Close()
$wbMaster.Save()
}
$counter += 1
$wsL4.Protect("PASSWORD")
$wbL4.Save()
$wbL4.Close()
}
$wsMaster.Protect("PASSWORD")
$wbMaster.Save()
$wbMaster.Close()
$xlMaster.Quit()
$EndTime = Get-Date -Format g
$TimeTotal = New-Timespan -Start $StartTime -End $EndTime
Write-Host $TimeTotal
To continue pipeline processing with the next input object, use return - not continue - in the script block passed to the ForEach-Object cmdlet.
The following simple example skips the 1st object output by Get-ChildItem and passes the remaining ones through:
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { return }; $_ }
There is currently (PSv5.1) no direct way to stop the processing of further input objects - for workarounds, see this answer of mine.
By contrast, as you've discovered, break and continue only work as expected in the script block of a for / foreach statement, not directly in the script block passed to the ForeEach-Object cmdlet:
For instance, the following produces no output (using break would have the same effect):
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { continue }; $_ }
The reason is that continue and break look for an enclosing for / foreach statement to continue / break out of, and since there is none, the entire command is exited; in a script, the entire script is exited if there's no enclosing for / foreach / switch statement on the call stack.

Using -eq across two datasets in powershell

I'm in a juncture here. I have two datasets in powershell. Dataset 1($table) is received via an sql query (varies from 12 to 17 rows and has 8 columns) and Dataset 2($team) is hard coded in the script (has 18 rows and 2 columns). Both of these have a common column, Contest. Now the script I have to get working is - for each Contest in $table.contest, get other corresponding parameters from $table and match the Contest in $team.contest and get the corresponding $team.column2 value into play.
I'm able to get the data individually from each table, but when I use "-eq" condition across $table.contest & $team.contest, nothing happens.
This is the snippet from the code where I'm facing the problem.
$Contests = ($DataSet.Contest)
$Team = ($Team.cont)
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$newbies = $stats.Newbies
$uploads = $stats.Uploads
$views = $stats.Views
$eviews = $stats.EViews
$votes = $stats.Votes
$date = $stats.EndDate
$teamx = $team | where {$_ -eq $stats.contest}
$contest
$teamx
}
$contest shows the contest name, but $teamx is blank
The following is the changed code with respect hash tables. I tried to convert object array to string but in vain.
$team = #{
"Short Film" = "Member4";
"Student Photography" = "Member0";
"Student Art" = "Member1";
"Macro Photography" = "Member2";
"Landscape Photography" = "Member3";
}
$Contests = ($DataSet.Contest)
$Contests = $Contests | where {$_ -ne "" -and $_ -ne $null -and $_ -ne [dbnull]::value}
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$datatemp = $stats.Contest
if ($team.ContainsKey($datatemp)) {write-output "Exists"}
else {write-output "Doesn't Exist"}
$datatemp
$team.count
}
I tried directly feeding $Contest, $ContestName, and $stast.Contest inside ContainsKey, but all the time output is the same -
Doesn't Exist
Short Film
5
Doesn't Exist
Student Photography
5
Doesn't Exist
Student Art
5
Doesn't Exist
Macro Photography
5
Doesn't Exist
Landscape Photography
5
What am I doing wrong?
I can't tell for sure without knowing exactly what's in $Dataset, but your symptoms all point to trailing whitespace in the Contest value that's causing your tests to fail.
Try this and see if you get different results:
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$datatemp = $stats.Contest.trim()
if ($team.ContainsKey($datatemp)) {write-output "Exists"}
else {write-output "Doesn't Exist"}
$datatemp
$team.count
}