Issues getting column width set when creating a csv file from Powershell - arrays

I am having to update 500+ machines at work not knowing the patching status of them and what they will need patched. This will just be a script that will tell me which machines are online or offline so I know what to target with other scripts that are yet to be written.
This will output to a CSV file and it can be opened successfully with all of the data in the proper columns.
How do I get the column width of the columns in the CSV file to be a set size or autosized to the data in the columns?
I can get the data formatted the way I would like in Powershell. it will display properly onscreen. All of the ways I have tried to export it do not provide the correct result.
Also if possible, can background color, font, bold, etc be set from powershell and sent to the CSV file? Future scripts that I will have will be creating CSV files with 50+ columns and over 500 rows.
# Setting Time Variable
$Script_Time = (Get-Date -Format MM-dd-yyyy--HH-mm)
# XXXXXXXX Path mapping for Computer Names, Ping Logs, CSV Name
$Root_Path = $env:USERPROFILE + "\xxxx-Working-Path----\"
$Computer_Names_Path = $root_Path + "Computer_Names\"
$Computer_Names_File = $Computer_Names_Path + "\ComputerNames_Working.txt"
$Ping_Logs_Path = $Root_Path + "Logs\Ping_Logs\"
$Log_Csv_Name = $Ping_Logs_Path + "Ping Results--" + $Script_Time + ".csv"
# Blanking/Setting needed variables
$Path_Result = ""
$Ping_Result = ""
$Name = ""
$Machine_Status = ""
$Total_Count = 0
$Online_Count = 0
$Offline_Count = 0
# Gathering Computer Names from text file
$List_Of_Computers = Get-Content "$Computer_Names_File"
# Array creation for CSV output
$Results_Array = [System.Collections.ArrayList]#()
# Testing to see if folders exist
If (Test-Path $Computer_Names_Path){
$Path_Result = "Computer Names Path Exists"
Write-Output $Path_Result
}
Else{
$Path_Result = "Computer Names Path Does Not Exist"
Write-Output $Path_Result
Pause
}
If (Test-Path $Ping_Logs_Path){
$Path_Result = "Ping Logs Csv Path Exists"
Write-Output $Path_Result
}
Else{
$Path_Result = "Ping Logs Csv Path Does Not Exist"
Write-Output $Path_Result
Pause
}
# Loop to gather online status of each machine
Foreach ($Name in $List_Of_Computers) {
# Test for True result
If (test-connection -computername $name -buffersize 16 -count 1 -quiet){
$Ping_Result = "Online"
$Online_Count = $Online_Count + 1
}
# Test for false result
Else{
$Ping_Result = "Offline"
$Offline_Count = $Offline_Count + 1
}
# Displays Online status and adds to CSV Array
$Machine_Status = $Name + "," + $Ping_Result + "," + (Get-Date -Format MM-dd-yyyy--HH-mm-ss)
Write-Output $Machine_Status
[void]$Results_Array.add($Machine_Status)
}
# Total machine count
$Total_Count = $Results_Array.Count
# Adding Information to Top 2 rows in Array
$Results_Array.Insert(0,"No of Machines: $Total_Count,Online Total: $Online_Count,Offline Total: $Offline_Count")
$Results_Array.Insert(1,"Computer Name,Online Status,Timestamp")
# Sends Array to CSV File
set-content $Log_Csv_Name -Value $Results_Array
EDIT
Applied what I had to a Excel Application output. Thanks for pointing my in that direction. It is working perfectly. Just need to finish fine tuning the formatting that I desire and learn how to save it with Powershell. Both, I assume, I will be able to research easily.
Here is the new script I have come up with after researching and applying what I found. Hopefully it will help others if they stumble upon this.
# Setting Time Variable
Write-Output "Setting Time Variable" ""
$Script_Time = (Get-Date -Format MM-dd-yyyy--HH-mm)
# XXXXXXXX Path Mapping for Computer Names and Ping Logs
Write-Output "Setting Path Variable" ""
$Root_Path = $env:USERPROFILE + "\XXXXXXXXXXX\"
$Computer_Names_Path = $root_Path + "Computer_Names\"
$Computer_Names_File = $Computer_Names_Path + "\ComputerNames_Working.txt"
$Ping_Logs_Path = $Root_Path + "Logs\Ping_Logs\"
# Testing to see if Folders Exist
Write-Output "Testing to see if Folder Paths exist" ""
If (Test-Path $Computer_Names_Path){
$Path_Result = "Computer Names Path Exists"
Write-Output $Path_Result
}
Else{
$Path_Result = "Computer Names Path Does Not Exist"
Write-Output $Path_Result
Pause
}
If (Test-Path $Ping_Logs_Path){
$Path_Result = "Ping Logs Csv Path Exists"
Write-Output $Path_Result ""
}
Else{
$Path_Result = "Ping Logs Csv Path Does Not Exist"
Write-Output $Path_Result
Pause
}
# Creating Excel Application
Write-Output "Creating Excel and Setting up the Sheet" ""
$New_Excel = New-Object -ComObject Excel.Application
# Makes Excel Visable
$New_Excel.Application.Visible = $true
$New_Excel.DisplayAlerts = $false
# Creating Excel WorkBook
$New_Book = $New_Excel.Workbooks.Add()
# Sets the Worksheet and Names it
$New_Sheet = $New_Book.Worksheets.Item(1)
$New_Sheet.name = "Ping Results--" + (Get-Date -Format MM-dd-yyyy--HH-mm)
# Selects the Worksheet
$New_Sheet.Activate()
# Blanking/Setting Needed Variables
Write-Output "Setting Start Variables" ""
$Path_Result = ""
$Ping_Result = ""
$Name = ""
$Machine_Status = ""
$Total_Count = 0
$Online_Count = 0
$Offline_Count = 0
$Row = 1
$Column = 1
# Creates the Title for the Sheet
Write-Output "Creating Title" ""
$New_Sheet.Cells.Item($Row,$Column) = "Ping Results"
$New_Sheet.Cells.Item($Row,$Column).Font.Size = 21
$New_Sheet.Cells.Item($Row,$Column).HorizontalAlignment = -4108
$New_Sheet.Cells.Item($Row,$Column).Font.Bold = $true
# Merging Cells for the Title
$New_Sheet.Range("A1:c1").Merge()
# Skipping Two Rows to leave space for Totals to be added at the end
$row = $Row + 2
# Creating Column Headers for the Data
Write-Output "Creating Column Headers" ""
$New_Sheet.Cells.Item($Row,$Column) = "Computer Name"
$New_Sheet.Cells.Item($Row,$Column).Font.Size = 16
$New_Sheet.Cells.Item($Row,$Column).Font.ColorIndex = 1
$New_Sheet.Cells.Item($Row,$Column).Font.Bold = $True
$column++
$New_Sheet.Cells.Item($Row,$Column) = "Online Status"
$New_Sheet.Cells.Item($Row,$Column).Font.Size = 16
$New_Sheet.Cells.Item($Row,$Column).Font.ColorIndex = 1
$New_Sheet.Cells.Item($Row,$Column).Font.Bold = $True
$column++
$New_Sheet.Cells.Item($Row,$Column) = "Timestamp"
$New_Sheet.Cells.Item($Row,$Column).Font.Size = 16
$New_Sheet.Cells.Item($Row,$Column).Font.ColorIndex = 1
$New_Sheet.Cells.Item($Row,$Column).Font.Bold = $True
# Sets the Column and Row for the First Set of Data
$Row = $Row + 1
$Column = 1
# Gathers the List of Computers
Write-Output "Gathering Computer Names" ""
$List_Of_Computers = Get-Content "$Computer_Names_File"
# Loop to get the status of each machine
Write-Output "Gathering Data from each Computer" ""
Write-Output "Computer Name Online Status Timestamp"
Foreach ($Name in $List_Of_Computers){
If (test-connection -computername $name -buffersize 16 -count 1 -quiet){
$Ping_Result = "Online"
$Online_Count = $Online_Count + 1
}
Else{
$Ping_Result = "Offline"
$Offline_Count = $Offline_Count + 1
}
$Machine_Status = $Name + " " + $Ping_Result + " " + (Get-Date -Format MM-dd-yyyy--HH-mm-ss)
Write-Output $Machine_Status
$New_Sheet.Cells.Item($row,$column) = $Name
$column = $Column + 1
$New_Sheet.Cells.Item($row,$column) = $Ping_Result
$Column = $Column + 1
$New_Sheet.Cells.Item($row,$column) = (Get-Date -Format MM-dd-yyyy--HH-mm-ss)
$Row = $row + 1
$Column = 1
}
# Adding Totals Information to Row 2
$New_Sheet.Cells.Item(2,1) = "# of Machines: " + $List_Of_Computers.Count
$New_Sheet.Cells.Item(2,2) = "Online Total: " + $Online_Count
$New_Sheet.Cells.Item(2,3) = "Offline Total: " + $Offline_Count

Related

Powershell Check folders and send e-mail

I have a program which checks two paths for files and if there are any files it sends one mail for one person each.
They now need four paths checked and multiple person need a mail for a path.
e.g.
Path 1 - Mail to x, y and z
Path 2 - Mail to a and b
Path 3 - Mail to x and a
Path 4 - Mail to s, r and w
How can I make it easy and most efficient?
$folders = #()
$folders += "\\server\files\Info\test"
$folders += "\\server\files\Info\test2"
$receiver = #()
$receiver += "person1#test.com"
$receiver += "person2#test.com"
$i = 0
$folders | ForEach-Object {
$checkforfiles = $_
$directoryInfo = Get-ChildItem $checkforfiles | Measure-Object
$directoryInfo.count #Returns the count of all of the objects in the directory
$Numberone = $directoryInfo.Count
if ($directoryInfo.count -ge 1){
send-mailmessage -subject "Subject" -Body "There are $checkforfiles files " -from foldercheck#test.com -to $receiver[$i] `
-smtpserver smtp.ser.com
$i = $i+1
}
else{
write-host "nothing to process"
}
}
You could simply extend the array of folders to test and use a switch to determine which users should get an email.
Also, I would advise using splatting on cmdlets with a lot of parameters to make for cleaner code.
# an array of folder paths to test
$folders = "\\server\files\Info\test", "\\server\files\Info\test2", "\\server\files\Info\test3", "\\server\files\Info\test4"
for ($i = 0; $i -lt $folders.Count; $i++) {
# get the file (and folder) count inside
$count = #(Get-ChildItem -Path $folders[$i]).Count
if ($count -gt 0) {
# determine who to email
$to = switch ($i) {
0 { 'personX#test.com', 'personY#test.com', 'personZ#test.com' ; break }
1 { 'personA#test.com', 'personB#test.com' ; break }
2 { 'personX#test.com', 'personA#test.com' ; break }
3 { 'personS#test.com', 'personR#test.com', 'personW#test.com' }
}
# set up a hashtable with parameters for splattting to Send-MailMessage
$mailParams = #{
To = $to
From = 'foldercheck#test.com'
Subject = "Subject"
Body = "There are $count files in folder '$($folders[$i])'"
SmtpServer = 'smtp.ser.com'
# add more parameters here if needed
}
# send the email
Send-MailMessage #mailParams
}
else {
Write-Host "Empty folder '$folders[$i]'; Nothing to process"
}
}

Powershell Script Taking Long Time when seems like it shouldnt?

So I have a script that is meant to send some pretty huge (700mb) txt files to an FTP of a service we use that automatically set's our prices across a fleet of domains.
I'm using a bitearray (as found here) to upload it to the site, and I have some very elementary built in error handling as well as some database tie-ins.
If we run these seperately, they move pretty fast. For whatever reason, after the completion of one block, the time until starting another block is CRAZY volatile. Sometimes it's 2 minutes, sometimes the script just sits around for a good 40 minutes before moving to the next block.
I'm sort of assuming the issue is I'm giving the thing more housekeeping than I should be? It's also worth noting that even stopping the script can sometimes take 15 minutes. (Like, If I just hit break on the script in the middle of it running, it can take a good 15-20 minutes to stop)
Also, for what it's worth, the script got MUCH worse in terms of runtime in the last few days. I have no idea what we could have changed to make it start taking so much longer but here we are.
Anyway, any insights would be appreciated.
Notes: I don't clear the content variable when I clear variables, should I?
Should I keep the rs open? I don't think I CAN because I'm connecting to the FTP with different usernames.
Here's the code (I actually have about 12 of the *.txt Blocks, but They're identicle so I've kept it down to three here):
#================================== SETUP BLOCK ==================================#
#get dat email ready
$strpasswd = Get-Content "PASSWORDFILE" | ConvertTo-SecureString
$mycreds = New-Object System.Management.Automation.PSCredential ("EXCHANGEUSER",$strpasswd)
$EmailTo = 'some#email','goes#here'
$EmailFrom = 'EXCHANGEUSER'
$EmailSubject = "CA Feed Issue Undefined Subject"
$emailbody = "Body Not Yet Defined"
$SmtpServer = 'MUHSERVER'
#Opens up database session so we can send queries
$strserver = "Server\MUHDB"
$strdatabase = "logs"
$strusername = "EXCHANGEUSER"
#createsdatabaseconnection
$sqlConnection = new-object System.Data.SqlClient.SqlConnection "server='$strserver';database='$strdatabase';Integrated Security=SSPI; User ID='$strusername'; password='$strpassword'"
$sqlConnection.Open()
#define the defaultquery
$strQuery =
"
INSERT INTO [logs].[dbo].[EventLog] (SourceID, Started, Completed, Result, Context, Machine)
values (50,1500,1500,'NOTEDEFINED','NOTDEFINED','Server\MUHCLIENTMACHINE-CAFeed')
"
#this is how I execute the command
#$sqlCommand = $sqlConnection.CreateCommand()
#$sqlCommand.CommandText = $strquery
#$sqlCommand.ExecuteReader()
#==================================Luna.txt ==================================#
##DEFINE THESE TO CREATE NEW FEEDS
$strFilename = "\\PATH\Luna.txt"
$ftp = [System.Net.FtpWebRequest]::Create("FTPLINK1")
$user = "USERNAME1"
$password = "PASSWORDREDACTED"
# create the FtpWebRequest and configure it
$ftp = [System.Net.FtpWebRequest]$ftp
# build authentication and connection
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = new-object System.Net.NetworkCredential($user,$password)
$ftp.UseBinary = $true
$ftp.UsePassive = $true
$ftp.timeout = -1
#start a timer and error handling
$starttime = (get-date).ToString()
$error.Clear()
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes("$strfilename")
$ftp.ContentLength = $content.Length
# get the request stream, and write the bytes into it
$rs = $ftp.GetRequestStream()
$rs.Write($content, 0, $content.Length)
$endtime = (get-date).ToString()
#error handle
if ($error)
{
#Assemble the Query
$sqlresult = "THERE IS AN ERROR, Check the error email for details"
$sqlcontext = ($strfilename + '|' + $content.length + ' bytes')
$strquery =
"INSERT INTO [logs].[dbo].[EventLog] (SourceID, Started, Completed, Result, Context, Machine)
values (50,'$starttime','$endtime','$sqlresult','$sqlcontext','Server\MUHCLIENTMACHINE-CAFEEDSCRIPT')"
#Create Command and Execute.
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = $strQuery
$sqlCommand.ExecuteNonQuery()
#Send dem emails
$emailbody = "A file for the CA Feed failed on $strfilename at " + (get-date).ToString() + " with the error '$error[0]'"
$emailsubject = "CA Feed Failed File"
Send-MailMessage -SmtpServer $SmtpServer -to $EmailTo -from $EmailFrom -subject $EmailSubject -Body $emailbody
}
else
{
write-host ("$strfilename" + ' Ran Without Errors')
$sqlresult = "RAN WITHOUT ERRORS"
$sqlcontext = ($strfilename + '|' + $content.length + ' bytes')
$strquery =
"INSERT INTO [logs].[dbo].[EventLog] (SourceID, Started, Completed, Result, Context, Machine)
values (50,'$starttime','$endtime','$sqlresult','$sqlcontext','Server\MUHCLIENTMACHINE-CAFEEDSCRIPT')"
#Create a command object.
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = $strQuery
$sqlCommand.ExecuteNonQuery()
}
# be sure to clean up after ourselves and get ready for next block
Clear-Variable -Name starttime,endtime,strfilename,sqlresult,sqlcontext,ftp
$rs.Close()
$rs.Dispose()
#==================================LDE.txt ==================================#
##DEFINE THESE TO CREATE NEW FEEDS
$strFilename = "\\PATH\LDE.txt"
$ftp = [System.Net.FtpWebRequest]::Create("FTPLINK2")
$user = "USERNAME2"
$password = "PASSWORDREDACTED"
# create the FtpWebRequest and configure it
$ftp = [System.Net.FtpWebRequest]$ftp
# build authentication and connection
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = new-object System.Net.NetworkCredential($user,$password)
$ftp.UseBinary = $true
$ftp.UsePassive = $true
$ftp.timeout = -1
#start a timer and error handling
$starttime = (get-date).ToString()
$error.Clear()
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes("$strfilename")
$ftp.ContentLength = $content.Length
# get the request stream, and write the bytes into it
$rs = $ftp.GetRequestStream()
$rs.Write($content, 0, $content.Length)
$endtime = (get-date).ToString()
#error handle
if ($error)
{
#Assemble the Query
$sqlresult = "THERE IS AN ERROR, Check the error email for details"
$sqlcontext = ($strfilename + '|' + $content.length + ' bytes')
$strquery =
"INSERT INTO [logs].[dbo].[EventLog] (SourceID, Started, Completed, Result, Context, Machine)
values (50,'$starttime','$endtime','$sqlresult','$sqlcontext','Server\MUHCLIENTMACHINE-CAFEEDSCRIPT')"
#Create Command and Execute.
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = $strQuery
$sqlCommand.ExecuteNonQuery()
#Send dem emails
$emailbody = "A file for the CA Feed failed on $strfilename at " + (get-date).ToString() + " with the error '$error[0]'"
$emailsubject = "CA Feed Failed File"
Send-MailMessage -SmtpServer $SmtpServer -to $EmailTo -from $EmailFrom -subject $EmailSubject -Body $emailbody
}
else
{
write-host ("$strfilename" + ' Ran Without Errors')
$sqlresult = "RAN WITHOUT ERRORS"
$sqlcontext = ($strfilename + '|' + $content.length + ' bytes')
$strquery =
"INSERT INTO [logs].[dbo].[EventLog] (SourceID, Started, Completed, Result, Context, Machine)
values (50,'$starttime','$endtime','$sqlresult','$sqlcontext','Server\MUHCLIENTMACHINE-CAFEEDSCRIPT')"
#Create a command object.
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = $strQuery
$sqlCommand.ExecuteNonQuery()
}
# be sure to clean up after ourselves and get ready for next block
Clear-Variable -Name starttime,endtime,strfilename,sqlresult,sqlcontext,ftp
$rs.Close()
$rs.Dispose()
I don't think anybody is going to debug that code. Your best bet is to find where your issue is. I use a stopwatch like below. set it up strategically:
$SW = [System.Diagnostics.Stopwatch]::new()
$SW.Start()
#Your code block goes here
Write-Host "End of Code block 1"
$SW.Elapsed.TotalSeconds
#Another code block goes here
Write-Host "End of Code block 2"
$SW.Elapsed.TotalSeconds
Now if you are trying to break out and it is taking 15 mins to respond, it is probably stuck doing an operation. It cannot respond until the operation finishes or fails.

Not able to access shared path in powershell

I want to take a SQL DB backup to a shared path and perform restoration through PS. Below is the code to perform the op
function restore-TemporaryInLocal($dbName,$backupPath,$sqlServer,$isOverWrite,$DecDBObj,$sqlInstanceName)
{
$percentEventHandler = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] { Write-Host "Restoration progress : " $_.Percent "%" }
$completedEventHandler = [Microsoft.SqlServer.Management.Common.ServerMessageEventHandler] { Write-Host $_.Error.Message}
# Get the default file and log locations
# (If DefaultFile and DefaultLog are empty, use the MasterDBPath and MasterDBLogPath values)
$DataFileLoc = $sqlServer.Settings.DefaultFile
$LogFileLoc = $sqlServer.Settings.DefaultLog
if ($DataFileLoc.Length -eq 0) {
$DataFileLoc = $sqlServer.Information.MasterDBPath
}
if ($LogFileLoc.Length -eq 0) {
$LogFileLoc = $sqlServer.Information.MasterDBLogPath
}
# Identify the backup file to use, and the name of the database copy to create
#$BackupFile= 'D:\SQLJobs\Errdb2.bak'
#$RestoredDBName = 'ErrDB2_20101016'
# Build the physical file names for the database copy
$DBFile = $DataFileLoc + $dbname + '_Data.mdf'
$LogFile = $LogFileLoc + $dbname + '_Log.ldf'
# Use the backup file name to create the backup device
$bdi = new-object ('Microsoft.SqlServer.Management.Smo.BackupDeviceItem') ($backupPath, 'File')
# Create the new restore object, set the database name and add the backup device
$restoreObj = new-object('Microsoft.SqlServer.Management.Smo.Restore')
$restoreObj.Database = $dbName
$restoreObj.Devices.Add($bdi)
# Get the file list info from the backup file
$FileList = $restoreObj.ReadFileList($sqlServer)
foreach ($file in $FileList) {
$rsfile = new-object('Microsoft.SqlServer.Management.Smo.RelocateFile')
$rsfile.LogicalFileName = $file.LogicalName
if ($file.Type -eq 'D'){
$rsfile.PhysicalFileName = $DBFile
}
else {
$rsfile.PhysicalFileName = $LogFile
}
$restoreObj.RelocateFiles.Add($rsfile)
}
# Restore the database
$restoreObj.ReplaceDatabase=$isOverWrite;
#Write-Host "Replace DB :"$restoreObj.ReplaceDatabase
$restoreObj.add_PercentComplete($percentEventHandler)
$restoreObj.add_Complete($completedEventHandler)
if($isOverWrite -eq $true)
{
Write-Host "Taking DB Offline";
$script="ALTER DATABASE $dbname SET OFFLINE WITH ROLLBACK IMMEDIATE";
#Write-Host $script $dbname $sqlInstanceName
Invoke-SQLcmd -Server $sqlInstanceName -Database $dbName $script
Write-Host "Getting DB Online";
$DecDBObj.SetOnline();
}
Write-Host "Initiating DB restore..."
$restoreObj.SqlRestore($sqlServer)
}
function take-BackupToLocal($backupDirectory,$serverName,$backupDB,$isCopyOnly,$serverRef)
{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
$percentEventHandler = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] { Write-Host "Database backed up" $_.Percent "%" }
$completedEventHandler = [Microsoft.SqlServer.Management.Common.ServerMessageEventHandler] { Write-Host $_.Error.Message
Write-Host "Backup Path:"$targetPath
}
$dbList = $serverRef.Databases
$BackupDB =$BackupDB.Trim();
$DBexists=$false;
if(test-path $backupDirectory)
{
$timestamp = Get-Date -format yyyy-MM-dd-HHmmss
$targetPath = $backupDirectory + "\" + $backupDB + "_" + $timestamp + ".bak"
foreach ($database in $dbList | where { $_.IsSystemObject -eq $False })
{
$dbName = $database.Name
if($database.Name -eq $BackupDB -and $database.Status -ieq "normal"){
$DBexists=$true;
$smoBackup = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")
$smoBackup.Action = "Database"
$smoBackup.BackupSetDescription = "Full Backup of " + $dbName
$smoBackup.BackupSetName = $dbName + " Backup"
$smoBackup.Database = $dbName
$smoBackup.MediaDescription = "Disk"
$smoBackup.CopyOnly=$isCopyOnly
$smoBackup.Devices.AddDevice($targetPath, "File")
$smoBackup.PercentCompleteNotification=5
$smoBackup.add_PercentComplete($percentEventHandler)
$smoBackup.add_Complete($completedEventHandler)
$smoBackup.SqlBackup($serverRef)
return $targetPath;
}else{
Write-Host "Cannot perform a backup as the DB is in:" $database.Status " mode";
}
}
if($DBexists -eq $false)
{
Write-Host "$BackupDB DB not found in $serverName.Please check the DB & Server details"
}
}
else
{
Write-Host "Cannot find the path specified. Contact admin"
Write-Host "backup path:"$backupDirectory
}
}
$sqlName= "*************"
$serverName="******";
# Set new or existing databse name to restote backup
$backupDB= "AdventureWorksDW2012"
# Set the existing backup file path
#$backupPath= "D:\Automation\Ps-scripts\Backups\tempdb1_2017-05-25-222232.bak"
#Load the required assemlies SMO and SmoExtended.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
$percentEventHandler = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] { Write-Host "Restoration progress : " $_.Percent "%" }
$completedEventHandler = [Microsoft.SqlServer.Management.Common.ServerMessageEventHandler] { Write-Host $_.Error.Message}
try{
# Connect SQL Server.
$sqlServer = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $sqlName
$ExistingDB=$sqlServer.Databases.Item($backupDB)
$backupDirectory="\\***********\ps_jobs_backup_shared";
$isCopyOnly=$true;
if($ExistingDB.EncryptionEnabled -eq $true)
{
$needToDecrypt = Read-Host -Prompt 'Specify if backup needs to be decrypted (Y/N)'
}
if($needToDecrypt -ieq "y")
{
#Write-Host "decryption code...."
$decServerSqlName="***************"
Write-Host "Initiating Backup..."
$DecryptDB="tempDB1"
$decSqlServer = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $decServerSqlName
$decServerSqlName="****************"
$isOverWrite=$true;
$DecDBObj=$decSqlServer.Databases.Item($DecryptDB)
$backupPath=take-BackupToLocal -backupDirectory $backupDirectory -serverName $sqlName -backupDB $backupDB -isCopyOnly $isCopyOnly -serverRef $sqlServer
restore-TemporaryInLocal -dbName $DecryptDB -backupPath $backupPath -sqlServer $decSqlServer -isOverWrite $isOverWrite -DecDBObj $DecDBObj -sqlInstanceName $decServerSqlName
}elseif($needToDecrypt -ieq "n")
{
Write-Host "Initiating a encrypted backup...";
take-BackupToLocal -backupDirectory $backupDirectory -serverName $sqlName -backupDB $backupDB -isCopyOnly $isCopyOnly -serverRef $sqlServer
}
}
catch
{
$_.Exception.GetBaseException().Message
}
Now the above code works fine. However when I execute the same code once again the TEST-PATH conditions fails[work-around: Need to re-open the PS IDE again after every run]
Below is the log for first 2 runs.
Specify if backup needs to be decrypted (Y/N): y
Initiating Backup...
Database backed up 5 %
Database backed up 10 %
Database backed up 100 %
BACKUP DATABASE successfully processed 25625 pages in 8.435 seconds (23.733 MB/sec).
Taking DB Offline
Getting DB Online
Initiating DB restore...
Restoration progress : 10 %
Restoration progress : 20 %
Restoration progress : 100 %
RESTORE DATABASE successfully processed 25625 pages in 7.429 seconds (26.946 MB/sec).
2nd Run:
Specify if backup needs to be decrypted (Y/N): y
Initiating Backup...
Cannot find the path specified. Contact admin
backup path: \\sanky555\ps_jobs_backup_shared

Task scheduler creates corrupted version of generated by script file

I've been working on a little project in Powershell.
My task was to create a script that will collect all files from mail attachments, merge all .pdf files into one and send the generated file to my email.
The script works completely fine in Powershell ISE, but when I try to run it from task scheduler, the merged .pdf file is corrupted without any data in it.
Keep in mind I am new to this stuff.
This is my main code that does all the heavy work:
function getAttachments
{
#########################################################
##-----------------------------------------------------##
## GET ATTACHMENTS ##
##-----------------------------------------------------##
#########################################################
##PATH TO CREDENTIAL
$credpath = "C:\Users\" + $env:UserName + "\Documents\myCred_${env:USERNAME}_${env:COMPUTERNAME}.xml"
#test variable
$test = Test-Path $credpath
##TEST IF CREDENTIAL EXISTS
if(!$test){
## USER PROMPT PSW CREDENTIAL ##
$cred = Get-Credential
#save credential in documents
$cred | Export-CliXml -Path $credpath
}else{
##READ USER CREDENTIAL FROM FILE
$cred = Import-CliXml -Path $credpath
}
##url and date variables
$url = "https://outlook.office365.com/api/v1.0/me/messages"
$d = [DateTime]::Today.AddDays(-1)
$global:date = $d.ToString("yyyy-MM-dd")
## Get all messages that have attachments where received date is greater than $date
$messageQuery = "" + $url + "?`$select=Id&`$filter=HasAttachments eq true and DateTimeReceived ge " + $date
$messages = Invoke-RestMethod $messageQuery -Credential $cred
## Loop through each results
foreach ($message in $messages.value)
{
# get attachments and save to file system
$query = $url + "/" + $message.Id + "/attachments"
$attachments = Invoke-RestMethod $query -Credential $cred
# in case of multiple attachments in email
foreach ($attachment in $attachments.value)
{
Write-Host “Found File :- ” $attachment.Name
$path = "c:\Attachments\" + $attachment.Name
$Content = [System.Convert]::FromBase64String($attachment.ContentBytes)
Set-Content -Path $path -Value $Content -Encoding Byte
}
}
}
function sendAttachments
{
#############################################################
##---------------------------------------------------------##
## SEND ATTACHMENTS AND DELETE FILES ##
##---------------------------------------------------------##
#############################################################
#Connection Details
#PATH TO CREDENTIAL
$credpath = "C:\Users\" + $env:UserName + "\Documents\myCred_${env:USERNAME}_${env:COMPUTERNAME}.xml"
$cred = Import-CliXml -Path $credpath
$smtpServer = “ smtp.office365.com”
$msg = new-object Net.Mail.MailMessage
#Change port number for SSL to 587
$smtp = New-Object Net.Mail.SmtpClient($SmtpServer, 25)
#Uncomment Next line for SSL
$smtp.EnableSsl = $true
$smtp.Credentials = $cred
$msg.IsBodyHtml = $true
#From Address
$msg.From = $cred.UserName
#To Address, Copy the below line for multiple recipients
$msg.To.Add(“email#gmail.com”)
#Message Body
$msg.Body=”<h2>Alle attachments samen bevinden zich in de bijlage van did email</h2> <br/><br/>”
#Message Subject
$msg.Subject = “no-reply: Email met alle attachments”
#your file location
$files=Get-ChildItem “C:\Attachments\”
#attach the right file
$file = $global:pname
Write-Host “Attaching File :- ” $file
$attachment = New-Object System.Net.Mail.Attachment –ArgumentList C:\Attachments\$file
$msg.Attachments.Add($attachment)
#send email
$smtp.Send($msg)
$attachment.Dispose();
$msg.Dispose();
#delete the files from the folder
Get-ChildItem -Path C:\Attachments -Include * -File -Recurse | foreach { $_.Delete()}
}
function mergePDF
{
#############################################################
##---------------------------------------------------------##
## MERGE ALL PDF FILES ##
##---------------------------------------------------------##
#############################################################
$workingDirectory = "C:\Attachments"
$itspath = $PSScriptRoot
$global:pname = $global:date + "_pdfAttachments.pdf"
$pdfs = ls $workingDirectory -recurse | where {-not $_.PSIsContainer -and $_.Extension -imatch "^\.pdf$"};
[void] [System.Reflection.Assembly]::LoadFrom([System.IO.Path]::Combine($itspath, 'itextsharp.dll'));
$output = [System.IO.Path]::Combine($workingDirectory, $pname);
$fileStream = New-Object System.IO.FileStream($output, [System.IO.FileMode]::OpenOrCreate);
$document = New-Object iTextSharp.text.Document;
$pdfCopy = New-Object iTextSharp.text.pdf.PdfCopy($document, $fileStream);
$document.Open();
foreach ($pdf in $pdfs) {
$reader = New-Object iTextSharp.text.pdf.PdfReader($pdf.FullName);
[iTextSharp.text.pdf.PdfReader]::unethicalreading = $true
$pdfCopy.AddDocument($reader);
$reader.Dispose();
}
$document.Close()
$pdfCopy.Dispose();
$document.Dispose();
$fileStream.Dispose();
}
getAttachments
Start-Sleep -s 10
mergePDF
Start-Sleep -s 10
sendAttachments
In this piece of code that I run in another script file, I create a new task:
#############################################################
##---------------------------------------------------------##
## SCHEDULE SCRIPTS IN WINDOWS TASKS ##
##---------------------------------------------------------##
#############################################################
##PATH TO CREDENTIAL
$credpath = "C:\Users\" + $env:UserName + "\Documents\myCred_${env:USERNAME}_${env:COMPUTERNAME}.xml"
#test variable
$test = Test-Path $credpath
##TEST IF CREDENTIAL EXISTS
if(!$test){
## USER PROMPT PSW CREDENTIAL ##
$cred = Get-Credential
#save credential in documents
$cred | Export-CliXml -Path $credpath
}
$taskName = "ManageEmailAttachments"
$taskExists = Get-ScheduledTask | Where-Object {$_.TaskName -like $taskName }
if($taskExists)
{
Get-ScheduledJob ManageEmailAttachments
Unregister-ScheduledJob ManageEmailAttachments
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Task successfully deleted, run the script again to schedule the task",0,"Done",0x0)
}
else
{
$tt = Get-Date
$tt = $tt.AddMinutes(1)
$testtime = $tt.ToString("HH:mm:ss")
#set trigger
$trigger = New-JobTrigger -Daily -At "1:00"
$testtrigger = New-JobTrigger -Daily -At $testtime
#path to the scripts
$scriptPath = $PSScriptRoot + "\wps_manage_pdf_attachments.ps1"
#options(optional)
$option = New-ScheduledJobOption -WakeToRun: $true
#create a new task
Register-ScheduledJob -Name ManageEmailAttachments -FilePath $scriptPath -Trigger $testtrigger -ScheduledJobOption $option
}
The script when run in Powershell works great, it gets all the attachments from mailbox, merges them into 1 .pdf file and sends them to the requested email address. But when scheduled in windows task scheduler it does the first step fine, but when merged, the .pdf file is corrupted without any content.
I couldn't figure out how to make it work so I posted a question on the forum.
Hope you guys find a way to figure it out.
Thanks in advance
Use below function to get script root directory.
Function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -scope 1).Value
Split-path $Invocation.MyCommand.Path
}
$scriptPath=Join-Path(Get-ScriptDirectory) 'wps_manage_pdf_attachments.ps1'
Apparently the problem nested itself in the main code. I used:
Try{...}
Catch{$_ | Out-File C:\errors.txt}
In mergePDF function to find out what the error was. Seems like the path to my ITextSharp.dll was incorrect. $PSScriptRoot that I used showed "C:\windows\windows32" instead of where the script actually is.
So what I did instead was add a line in my batch file to copy the itextsharp.dll to %Temp%:
xcopy Scripts\itextsharp.dll %Temp% /D >NUL 2>NUL
and then read the file from there with:
$itsPath = [System.IO.Path]::GetTempPath()
And everything works as it should be. I know this isn't the best way to do it but I had this batch file before to make the script run by just dubbleclicking it.
So adding a little line won't hurt.
I hope this helps anyone with the same problem.

Check for file in ftp folder using powershell

I'm looking to use powershell to see if there is a specific file in an ftp folder. More specifically, there is roughly 15 different folders that need to be checked for a specific file name. Any ideas on how I would do this?
There is a PowerShell ftp module here.
$DEBUG = 1
# Machines
$MachineNames = #("machine1","machine2" )
$MachineIPs = #("192.168.1.1","192.168.1.2" )
# Websites
$WebsiteNames = #("website1","website2" )
$WebsiteURLs = #("http://yahoo.com","http://google.com" )
#====== check websites
$i = 0;
foreach ($WebsiteURL in $WebsiteURLs){
# First we create the request.
$HTTP_Request = [System.Net.WebRequest]::Create($WebsiteURL)
# We then get a response from the site.
$HTTP_Response = $HTTP_Request.GetResponse()
# We then get the HTTP code as an integer.
$HTTP_Status = [int]$HTTP_Response.StatusCode
#$HTTP_Response
If ($HTTP_Status -eq 200) {
if ($DEBUG -eq 1) {Write-Host "== " $WebsiteNames[$i] " is OK!" }
}
Else {
if ($DEBUG -eq 1) {Write-Host "==Error: "$WebsiteNames[$i] " may be down!" }
SendEmails $WebsiteNames[$i]
}
# Finally, we clean up the http request by closing it.
$HTTP_Response.Close()
Clear-Variable HTTP_Response
$i = $i + 1
}
#====== check IP
$i = 0;
foreach ($MachineIP in $MachineIPs){
$isValidIP = Test-Connection $MachineIP -Count 1 -Quiet
if ($DEBUG -eq 1) {
$hostn = [System.Net.Dns]::GetHostEntry($MachineIP).HostName
New-Object -TypeName PSObject -Property #{'Host'=$hostn;'IP'=$MachineIP}
}
if (-not($isValidIP)) {
if ($DEBUG -eq 1) {Write-Host "==Error: " $MachineNames[$i] " ("$MachineIPs[$i]") may be down!" }
SendEmails $MachineNames[$i]
}
$i = $i + 1
}

Resources