Global Variable Not Getting Value - powershell-4.0

I am wanting to get the row count from each iteration of my Function and assign it to a variable so I can send the results in an email at the end. My syntax produced an error of this which says to me the $rowcount is never actually being assigned to it?
Send-MailMessage: Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
#Declaring Global Variable
$myArray = $null
$GoodSyntax = "Select * From tableunknown"
$extractFile = "C:\Test.csv"
$dirName = "C:\Completed\"
$date = Get-Date -f 'MM.dd.yy'
#Call function
Execute-SQLquery
if (Execute-SQLquery $GoodSyntax)
$EmailBody = $myArray | Out-String
send-mailmessage -to "abc123#gmail.com" -from "barkbarksalon123#gmail.com" -Body $EmailBody -BodyAsHtml:$true -subject "Testing Through Powershell"
Function Execute-SQLquery {
param ($GoodSyntax)
$server = "Server01"
$database = "database01"
$connectionTemplate = "Data Source={0};Integrated Security=SSPI;Initial Catalog={1};"
$connectionString = [string]::Format($connectionTemplate, $server, $database)
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$command = New-Object System.Data.SqlClient.SqlCommand
$command.CommandText = $QueryString
$command.Connection = $connection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $command
$DataSet = New-Object System.Data.DataSet
$rowCount = $SqlAdapter.Fill($DataSet)
if ($rowCount -gt 0)
{
[System.IO.Directory]::CreateDirectory($dirName)
$filename = [IO.Path]::GetFileNameWithoutExtension($extractFile) + "_$date" + [IO.Path]::GetExtension($extractFile)
$extractFile = Join-Path $dirname $filename
$DataSet.Tables[0] | Export-Csv $extractFile -NoTypeInformation
$myArray += $rowCount
}
$connection.Close()
}

$rowCount isn't global ... global variables are listed at the top of your code ... it should read like this ...
#Declaring Global Variable
$myArray = $null
$rowCount = 0
$GoodSyntax = "Select * From tableunknown"
...
Make sense?
EDIT:
My bad! I see you are adding $rowCount to $myArray ... that wasn't clear from your question.
Try this ...
#Declaring Global Variable
$rowCountTotal = 0
$GoodSyntax = "Select * From tableunknown"
...
$EmailBody = $rowCountTotal | Out-String
...
$DataSet.Tables[0] | Export-Csv $extractFile -NoTypeInformation
$rowCountTotal += $rowCount
}

Related

How to check Time synchronization for domain controllers?

I am trying to write a PowerShell script to alert me if one of the domain controllers goes out of sync via an email, I tried to run the script, but I had a problem with sending the email, here is the code. Could you please help me and tell me what is missing in my code? I did not receive any email, so how can I send the script results to my email?
function Get-Time {
<#
.SYNOPSIS
Gets the time of a windows server
.DESCRIPTION
Uses WMI to get the time of a remote server
.PARAMETER ServerName
The Server to get the date and time from
.EXAMPLE
PS C:\> Get-Time localhost
.EXAMPLE
PS C:\> Get-Time server01.domain.local -Credential (Get-Credential)
#>
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$ServerName,
$Credential
)
try {
if ($Credential) {
$DT = Get-WmiObject -Class Win32_LocalTime -ComputerName $servername -Credential $Credential
} else {
$DT = Get-WmiObject -Class Win32_LocalTime -ComputerName $servername
}
} catch {
throw
}
$w32tm = Invoke-Command -Computer $Servers -ArgumentList $Servers -Scriptblock {
Param ($Servers)
foreach ($Server in $Servers) {
$Check = w32tm /monitor /computers:$Server /nowarn
$ICMP = (($Check | Select-String "ICMP")-Replace "ICMP: " , "").Trim()
$ICMPVal = [int]($ICMP -split "ms")[0]
$Source = w32tm /query /source
$Name = Hostname
switch ($ICMPVal) {
{$ICMPVal -le 0} {$Status = "Optimal time synchronisation"}
#you probably need another value here since you'll get no status if it is between 0 and 2m
{$ICMPVal -lt 100000} {$Status = "0-2 Minute time difference"}
{$ICMPVal -ge 100000} {$Status = "Warning, 2 minutes time difference"}
{$ICMPVal -ge 300000} {$Status = "Critical. Over 5 minutes time difference!"}
}
$String = $Name + " - $Status " + "- $ICMP " + " - Source: $Source"
Write-Output $String
}
}
$Servers = "localhost","DC001"
$Servers | Foreach {
Get-Time $_
$results = foreach ($Server in $Servers) {
Get-Time $Server
}
$Servers = "localhost","DC001"
$From = "abc#company.com"
$To = "abc#company.com"
$Cc = ""
$Subject = "Time Skew Results"
$Body = $Servers | ConvertTo-Html | Out-String
$SMTPServer = "imail.company.com"
Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -BodyAsHTML
}
}
I wrote the code again and it works now, here is the code:
$w32tm = Invoke-Command -Computer $Servers -ArgumentList $Servers -Scriptblock {
Param ($Servers)
Foreach ($Server in $Servers)
{
$Check = w32tm /monitor /computers:$Server /nowarn
$ICMP = (($Check | Select-String "ICMP")-Replace "ICMP: " , "").Trim()
$ICMPVal = [int]($ICMP -split "ms")[0]
$Source = w32tm /query /source
$Name = Hostname
Switch ($ICMPVal)
{
#{$ICMPVal -le 0} {$Status = "Optimal time synchronisation"}
#{$ICMPVal -lt 100000} {$Status = "0-2 Minute time difference"}
{$ICMPVal -ge 100000} {$Status = "Warning, 2 minutes time difference"}
{$ICMPVal -ge 300000} {$Status = "Critical. Over 5 minutes time difference!"}
}
if ($ICMPVal -gt 100000)
{
$String = "The Domain Controller: " + $Name + " has " + " - $Status " + " - $ICMP " + " - Source: $Source"
$From = "abc#company.com"
$To = "abc#company.com"
$Cc = ""
$Subject = "Time Synchronization Alert "
$Body = Write-Output $String
$SMTPServer = "imail.company.com"
Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -BodyAsHTML
}
}
}
$w32tm

Send-MailMessage : Cannot validate argument on parameter 'Subject'. [duplicate]

This question already has an answer here:
Send-MailMessage: Cannot validate argument on parameter 'Subject'
(1 answer)
Closed 5 years ago.
The emails will not send. Do i need some sort of a Try /Catch loop? What would be the best method of error checking this in Powershell?
I need it to check for each and if one location is not found that it still sends out the others and displays a message saying which Reports were sent and which were not depending on whether the report was found in the folder
Send-MailMessage : Cannot validate argument on parameter 'Subject'. The argument is null or empty. Provide an argument that is not null or empty,
and then try the command again.
#Defines Directory
$dir = "C:\Users\user\Desktop\reprts\Todays"
#Sets STMP server
$SMTPServer = "10.0.0.46"
#Declares todays time and formats
$Time = (Get-Date).ToString('MM/dd/yyyy hh:mm tt')
$japan = #{
Name = 'japan'
From = "me#me.com"
To = "you#you.com"
Cc = "him#him.com"
}
$ireland = #{
Name = 'ireland'
From = "me#me.com"
To = "you#you.com"
Cc = "her#her.com"
}
$spain = #{
Name = 'spain'
From = "me#me.com"
To = "you#you.com"
Cc = "her#her.com"
}
$_Regions = #()
$_Regions += New-Object PSObject -Property $japan
$_Regions += New-Object PSObject -Property $ireland
$_Regions += New-Object PSObject -Property $spain
ForEach ($_Region in $_Regions) {
#Searches dir for list , formats
$Attachment = Get-ChildItem -Path $dir -Filter "*$($_Region.name)*" -Recurse
$AttachmentName = $Attachment.BaseName
$Subject = "$AttachmentName"
$Body = "Please find attached the Missing Image Report for $($_Region.name).
Produced # $Time
Regards,
John Doe
"
#Actions Email
Send-MailMessage -From $_Region.From -To $_Region.To -CC $_Region.Cc -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Attachments $Attachment.FullName
$Attachment | Move-Item -Destination "C:\Users\user\Desktop\reprts\old"
}
I added some error handling.
The region-objects will be returned, if it send successfully or not, but with a message, where/why it failed
#Defines Directory
$dir = "C:\Users\user\Desktop\reprts\Todays"
#Sets STMP server
$SMTPServer = "10.0.0.46"
#Declares todays time and formats
$Time = (Get-Date).ToString('MM/dd/yyyy hh:mm tt')
$_Regions = #(
#{
Name = 'japan'
From = "me#me.com"
To = "you#you.com"
Cc = "him#him.com"
},
#{
Name = 'ireland'
From = "me#me.com"
To = "you#you.com"
Cc = "her#her.com"
},
#{
Name = 'spain'
From = "me#me.com"
To = "you#you.com"
Cc = "her#her.com"
}
)
ForEach ($_Region in $_Regions) {
$null = $_Region.add('action','Starting work')
#Searches dir for list , formats
try {
$Attachment = Get-ChildItem -Path $dir -Filter "*$($_Region.name)*" -Recurse -ErrorAction Stop
$AttachmentName = $Attachment.BaseName
$Subject = "$AttachmentName"
$Body = "Please find attached the Missing Image Report for $($_Region.name).
Produced # $Time
Regards,
John Doe
"
#Actions Email
Send-MailMessage -From $_Region.From -To $_Region.To -CC $_Region.Cc -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Attachments $Attachment.FullName -ErrorAction Stop
$Attachment | Move-Item -Destination "C:\Users\user\Desktop\reprts\old" -ErrorAction Stop
$_Region.action = 'success'
}
catch {
$_Region.action = $_ #catching the error
}
finally {
$_Region | Select-Object name, action
}
}

PowerShell Script Performance Optimization

I am running a PowerShell script that gets some information from a csv file, stores it in an object array and then does some action depending on what's on the file. It actually only does one thing:
If one column has a AD group it copies the row for every member of that group.
The thing is I am really new at scripting and at the beginning the files were small, so everything went ok. Now I have huge files and the script is taking hours to execute.
$file = "c:\Report.csv"
$fileContent = Import-csv $file | select *, UserName
foreach($item in $fileContent)
{
$LoginName = $item.LoginName
$LoginNameClean = $LoginName.split("\")
$LoginNameClean = $LoginNameClean[1].trimstart("_")
$ObjectClass = (Get-ADObject -filter {SamAccountName -eq $LoginNameClean}).ObjectClass
$UserName = $null
if($ObjectClass -eq "user")
{
$UserName = Get-ADUser -identity $LoginNameClean -properties DisplayName
if($UserName)
{
$item.UserName = $UserName.DisplayName
}
}
elseif($ObjectClass -eq "group")
{
$GroupUsers = Get-ADGroupMember -identity $LoginNameClean -Recursive
foreach($user in $GroupUsers)
{
$UserInsideGroup = Get-ADUser -identity $user -properties DisplayName
$UserInsideGroupName = $UserInsideGroup.DisplayName
$newRow = New-Object PsObject -Property #{"URL" = $item.URL; "SiteListFolderItem" = $item.SiteListFolderItem; "TitleName" = $item.TitleName; "PermissionType" = $item.PermissionType; "LoginName" = $item.LoginName; "Permissions" = $Item.Permissions; "UserName" = $UserInsideGroup.DisplayName;}
$fileContent += $newRow
}
}
}
$fileContent | Export-Csv -NoTypeInformation -Path "c:\ReportUpgraded.csv"
Any tips on how to improve the performance of this is much appreciated
Thanks in advance.
edit: I am using PS 2.0
As commentaries suggested, I am trying to replace the fileContent += newRow.
I am trying to use add member but it's giving me this error:
Add-Member : Cannot add a member with the name "URL" because a member
with that name already exists. If you wan t to overwrite the member
anyway, use the Force parameter to overwrite it. At line:1 char:26
+ $fileContent | Add-Member <<<< -MemberType NoteProperty -Name "URL"-Value "teste"
+ CategoryInfo : InvalidOperation: (#{SiteListFolde...me=; URL=teste}:PSObject) [Add-Member], Inv
alidOperationException
+ FullyQualifiedErrorId : MemberAlreadyExists,Microsoft.PowerShell.Commands.AddMemberCommand
How I can I use this properly? Add-member is not adding but replacing members
I manage to reduce 30 times the execution time with a couple of things.
First, I switched array to a array list so that I could use theArray.Add() method. Then, in order to stop making requests to the AD all the time, I am saving the information in excel sheets with the name of the group, so that it will only request AD once per group.
Here is the script:
$file = "ReportBefore.csv"
$fileContent = Import-csv $file | select *, UserName
[System.Collections.ArrayList]$ArrayList = $fileContent
foreach($item in $fileContent)
{
$LoginName = $item.LoginName
$LoginNameClean = $LoginName.split("\")
$LoginNameClean = $LoginNameClean[1].trimstart("_")
$ObjectClass = (Get-ADObject -filter {SamAccountName -eq $LoginNameClean}).ObjectClass
$UserName = $null
if($ObjectClass -eq "user")
{
$UserName = Get-ADUser -identity $LoginNameClean -properties DisplayName
if($UserName)
{
$item.UserName = $UserName.DisplayName
}
}
elseif($ObjectClass -eq "group")
{
$exportString = "\\folder\username$\Desktop\ADGroups\" + $LoginNameClean + ".csv"
if([System.IO.File]::Exists($exportString))
{
$GroupUsers = Import-csv $exportString | select *
}
else
{
$GroupUsers = Get-ADGroupMember -identity $LoginNameClean -Recursive | Select samAccountName,Name, #{Name="DisplayName";Expression={(Get-ADUser $_.distinguishedName -Properties Displayname).Displayname}}
$GroupUsers | Export-Csv -NoTypeInformation -Path $exportString
}
foreach($user in $GroupUsers)
{
$UserInsideGroupName = $user.DisplayName
$newRow = New-Object PsObject -Property #{"URL" = $item.URL; "SiteListFolderItem" = $item.SiteListFolderItem; "TitleName" = $item.TitleName; "PermissionType" = $item.PermissionType; "LoginName" = $item.LoginName; "Permissions" = $Item.Permissions; "UserName" = $UserInsideGroupName;}
#$filecontent += $newRow
$ArrayList.Add($newRow)
}
}
}
$ArrayList | Export-Csv -NoTypeInformation -Path "\ReportAfter.csv"

Write new lines to output

I have been trying to create an output file that writes multiple execute scripts taking a certain parameter from an array list. So far I am getting jumbled duplicated output. How can I get one execute command on each line? Here's what I have.
$myArray = #(1,2,3)
foreach ($element in $myArray) {
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'"
$myprocedure += $myobj
$myobj = $null
}
Out-file -filepath $path -inputobject $myprocedure -width 50 -force
$myprocedure is never initialized as an array, so it becomes a string that you simply add more text to. Either you need to add a linebreak at end of the execute lines:
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'" + [System.Environment]::NewLine
Or create an empty array called $myprocedure first:
$myArray = #(1,2,3)
$myprocedure = #()
$path = "test.txt"
foreach ($element in $myArray) {
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'"
$myprocedure += $myobj
$myobj = $null
}
Out-file -filepath $path -inputobject $myprocedure -width 50 -force
Or append 3 times to the file:
$myArray = #(1,2,3)
$path = "test.txt"
#remove-item $path if necessary
foreach ($element in $myArray) {
"EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'" | Out-file -filepath $path -width 50 -force -Append
}

Find the most recent file in a folder/dir and send that file in a email using shell script in windows cmd or powershell

I need to find the most recent file in a folder/dir and send that file as an attachment in a email, so far i have this code that find the most recent file in my windows SO, but i need to specify a route a find the most recent file there and then send that file in a email so far i have this:
EDIT 1:
So far i have this:
This part gives me the last file created in a dir/folder:
$dir = "D:\Users\myUser\Desktop\dirTest"
$latest = Get-ChildItem -Path $dir | Sort-Object LastAccessTime -Descending | Select-Object -First 1
$latest.Fullname
$attachment = $latest.Fullname
And this send the email (i'm using yahoo accounts):
$emailSmtpServer = "smtp.mail.yahoo.com"
$emailSmtpServerPort = "587"
$emailSmtpUser = "yahooAccountThatSendsTheEmail#yahoo.com"
$emailSmtpPass = "passForThisquestion"
$emailMessage = New-Object System.Net.Mail.MailMessage
$emailMessage.From = "yahooAccountThatSendsTheEmail#yahoo.com"
$emailMessage.To.Add( "yahooAccountThatRECIEVESTheEmail#yahoo.com" )
$emailMessage.Subject = "Testing e-mail"
$emailMessage.Body = "email from power shell"
$emailMessage.Attachments.Add( $attachment ) <---- this part gives me problems
$SMTPClient = New-Object Net.Mail.SmtpClient($emailSmtpServer, $emailSmtpServerPort)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailSmtpUser, $emailSmtpPass);
$SMTPClient.Send($emailMessage)
It works now, this is my final script.
This script search the most recent file created in a Dir and it sends that file created to a email account.
Here is my script it works for me but it takes a few minutes to send the email, thanks for the help
This script do what i wanted, it find the most recent file and send tha file in a email.
$dir = "d:\Users\myUser\Desktop\testDir"
$latest = Get-ChildItem -Path $dir | Sort-Object LastAccessTime -Descending | Select-Object -First 1
$latest.Fullname
$attachment = $latest.Fullname
$emailSmtpServer = "smtp.mail.yahoo.com"
$emailSmtpServerPort = "587"
$emailSmtpUser = "test_sender_mail_account#yahoo.com"
$emailSmtpPass = "MyPassword"
$emailMessage = New-Object System.Net.Mail.MailMessage
$emailMessage.From = "test_sender_mail_account#yahoo.com"
$emailMessage.To.Add( "test_receiver_mail_account#gmail.com" )
$emailMessage.Subject = "My Subject"
$emailMessage.Body = "My body message"
$emailMessage.Attachments.Add($attachment)
$SMTPClient = New-Object Net.Mail.SmtpClient($emailSmtpServer, $emailSmtpServerPort)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailSmtpUser, $emailSmtpPass);
$SMTPClient.Send($emailMessage)
With PowerShell, something like this should do alright:
function Send-RecentFile {
param(
[ValidateScript({Test-Path $_ })]
[String] $Path
)
$file = Get-ChildItem -Path $Path -File | Sort CreationTime | Select -Last 1
Write-Output "The most recently created file is $($file.Name)"
$messageParameters = #{
From = "myaccount#mydomain.com"
To = "destinationAccount#theirdomain.com"
Subject = "title"
Body = "message body"
SMTPServer = "mail.mydomain.com"
Attachments = $file.FullName
}
Send-MailMessage #messageParameters -Credential (Get-Credential "peopleo#anotherDomain.com")
}
If you do want to store the credentials in the file, you might have to do something slightly different with it.
Please check this powershell command.
$Body = “get the information here to show the data with attachement”
$dir = "Path\of\the\folder(C:\..\..)"
$latest = Get-ChildItem -Path $dir | Sort-Object LastAccessTime -Descending | Select-Object -First 1
$latest.Fullname
$file = $latest.Fullname
$EmailFrom = “sender#gmail.com”
$EmailTo = “receiver#gmail.com”
$SMTPServer = “smtp.gmail.com”
$EmailSubject = “Enter Your Subject”
$att = new-object Net.Mail.Attachment($file)
$mailmessage = New-Object system.net.mail.mailmessage
$mailmessage.from = ($EmailFrom)
$mailmessage.To.add($EmailTo)
$mailmessage.Subject = $EmailSubject
$mailmessage.Body = $Body
$mailmessage.IsBodyHTML = $true
$mailmessage.Attachments.Add($att)
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential(“sender_username”, “sender_password”);
$SMTPClient.Send($mailmessage)
$att.Dispose()

Resources