I have created a PowerShell script that does the following:
Copies a template Excel file to another directory
Imports data into this template file using the ImportExcel module created by Doug Finke ( https://www.powershellgallery.com/packages/ImportExcel/7.1.0 )
Runs a macro that is contained in the template file
Renames the file as an .xlsx file type
When I run this script from the server, it executes perfectly and does what is expected. But when I tried to set up a SQL Agent job to run this same PS script, it is failing and I cannot find the reason it would do so.
Here is my PS script:
# Directory Information
$RootDirectory = "D:\MSSQL\SSIS\BIN Commission Report\"
$ArchiveFolder = "Archive\"
$SourceFolder = "Source\"
$TemplateFolder = "Template\"
# File Information
$TemplateFile = "Commission_Invoices_Template.xlsm" #Template file with macro
$MacroFile = "Commission_Invoices.xlsm" #Macro-enabled file with vendor data
$VendorFile = "Commission_Invoices.xlsx" #File provided by the vendor
$DataImportFile = "Commission_Invoices_Import.xlsx" #File used to import data into SQL
$AppliedMacroFile = "Commission_Invoices_Import.xlsm"
#Full paths for each file
$TemplatePath = $RootDirectory, $TemplateFolder, $TemplateFile -Join ""
$MacroPath = $RootDirectory, $SourceFolder, $MacroFile -Join ""
$VendorPath = $RootDirectory, $SourceFolder, $VendorFile -Join ""
$DataImportPath = $RootDirectory, $SourceFolder, $DataImportFile -Join ""
$AppliedMacroPath = $RootDirectory, $SourceFolder, $AppliedMacroFile -Join ""
If ( Test-Path $MacroPath ) { Remove-Item $MacroPath }
If ( Test-Path $AppliedMacroPath ) { Remove-Item $AppliedMacroPath }
#Copy template file to use for date insertion and macro
Copy-Item $TemplatePath -Destination $MacroPath
# Copy data from vendor spreadsheet into macro-enabled workbook
Import-Excel -Path $VendorPath -WorksheetName Sheet1 | Export-Excel $MacroPath -WorksheetName Commission_Invoice
# Run the macro in Excel
$MacroName = "CommissionPrep"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$WB = $Excel.Workbooks.Add($MacroPath)
$Excel.Run($MacroName)
#Close Excel
$WB.Close($false)
$Excel.Quit()
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($WB)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
Remove-Variable -Name Excel
# Copy data to xlsx file from xlsm for data import into SQL
Import-Excel -Path $AppliedMacroPath -WorksheetName Commission_Invoice | Export-Excel $DataImportPath -WorksheetName Commission_Invoice -AutoSize
If ( Test-Path $MacroPath ) { Remove-Item $MacroPath }
If ( Test-Path $VendorPath ) { Remove-Item $VendorPath }
If ( Test-Path $AppliedMacroPath ) { Remove-Item $AppliedMacroPath }
This is what my SQL Agent job looks like:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File "D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1"
And here is the error message when run as a SQL Agent job:
Message
Executed as user: CompanyX\CompanyXSQLADMIN.
Microsoft Excel cannot access the file 'D:\MSSQL\SSIS\BIN Commission Report\Source\Commission_Invoices.xlsm'.
There are several possible reasons:
The file name or path does not exist.
The file is being used by another program.
The workbook you are trying to save has the same name as a currently open workbook.
At D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1:35 char:1 + $WB = $Excel.Workbooks.Add($MacroPath) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (:) [], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMExceptionException calling "Run" with "1" argument(s): "Cannot run the macro 'CommissionPrep'. The macro may not be available in this workbook or all macros may be disabled."
At D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1:36 char:1 + $Excel.Run($MacroName) + ~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : COMException
You cannot call a method on a null-valued expression.
At D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1:39 char:1 + $WB.Close($false) + ~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNullException calling "ReleaseComObject" with "1" argument(s): "Object reference not set to an instance of an object."
At D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1:45 char:1 + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WB) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : NullReferenceException 0 'D:\MSSQL\SSIS\BIN Commission Report\Source\Commission_Invoices_Import.xlsm' file not found
At C:\Program Files\WindowsPowerShell\Modules\ImportExcel\7.0.1\Public\Import-Excel.ps1:105 char:17 + throw "'$($Path)' file not found" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: ('D:\MSSQL\SSIS\... file not found:String) [], RuntimeException + FullyQualifiedErrorId : 'D:\MSSQL\SSIS\BIN Commission Report\Source\Commission_Invoices_Import.xlsm' file not found. Process Exit Code 1. The step failed.
I am unclear as to how this script executes perfectly when run from the PowerShell ISE shell when run locally on the server but fails it is run via a SQL Agent job. Any suggestions on how to fix this?
have you tried to set the executionpolicy for your SQL Agent job:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -File "D:\MSSQL\SSIS\BIN Commission Report\Scripts\CommissionInvoices_PS.ps1"
I've got a PowerShell script that I call from VBA using Excel. The script uses WinSCP to download some datetime-named FTP and SFTP files and saves them with a static filename, overwriting the old file, on a network drive location.
The script works on first run, but after that it loads the same cached version of the file. The workaround is to change the cache settings in IE to check for newer versions of stored webpages 'every time I visit the webpage'.
The macro is used by several people and is accessed using a variety of computers. Is there a way around this that I can incorporate in my code, either in VBA or PS so they don't have to remember to go into IE to change their settings?
Script is called from VBA:
Call Shell("powershell -executionpolicy bypass & ""H:\FTP\FTP.ps1""", vbHide)
Script:
try
{
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
$localPath = "H:\Worksheets\FTP"
$remotePath = "/outgoing/data/LatestData/"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::ftp
$sessionOptions.HostName =
$sessionOptions.UserName =
$sessionOptions.Password =
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Get list of files in the directory
$directoryInfo = $session.ListDirectory($remotePath)
# Select the most recent file
$latest = $directoryInfo.Files |
Where-Object { -Not $_.IsDirectory} |
Where-Object {
[System.IO.Path]::GetExtension($_.Name) -eq ".nc1" -or
[System.IO.Path]::GetExtension($_.Name) -eq ".ky1" -or
[System.IO.Path]::GetExtension($_.Name) -like ".tn*" }
Group-Object { [System.IO.Path]::GetExtension($_.Name) } |
ForEach-Object{
$_.Group | Sort-Object LastWriteTime -Descending | Select -First 1
}
$extension = [System.IO.Path]::GetExtension($latest.Name)
"GetExtension('{0}') returns '{1}'" -f $fileName, $extension
if ($latest -eq $Null)
{
Write-Host "No file found"
exit 1
}
$latest | ForEach-Object{
$extension = ([System.IO.Path]::GetExtension($_.Name)).Trim(".")
$session.GetFiles($session.EscapeFileMask($remotePath + $_.Name), "$localPath\$extension.txt" ).Check()
}
$stamp = $(Get-Date -f "yyyy-MM-dd-HHmm")
$filename = $stamp.subString(0,$stamp.length-6)
$session.GetFiles(
($remotePath + $fileName),
($localPath + $fileName + "." + $stamp)).Check()
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch [Exception]
{
Write-Host $_.Exception.Message
exit 1
}
For some reason I can't get this variable ($SelectedDrive) to store data in it using the Get-Content cmdlet.
I have created a function as shown below:
#Allow user to select which drive to backup to and checks that the user hasn't canceled out of the Drive Selection GUI
Function SelectDrive{
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content $locBackupFolder\Drive.txt
IF ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
Exit
} ELSE {
$BackupDrive = $SelectedDrive.Substring(0,2)
}
}
When I run the script in Powershell ISE using the F5 button it runs through, and then fails to enter the data.
I go back and check it over and can't see any issue, as it works if I run it piece by piece using Run Selection.
The whole script is below:
######### DEFINE FUNCTIONS #########
$locBackupFolder = "C:\Backup"
#Check Backup Drive
IF (!(Test-Path $locBackupFolder)) {
#C:\Backup does NOT exist
New-Item $locBackupFolder -ItemType Directory
}
#Inform user only D:\Username will be backed up
function WarningDialog(
$MessageWarning = "!!!WARNING!!!
YOU MAY LOSE DATA IF YOU DO NOT ADHERE TO THIS MESSAGE
Only D:\$env:USERNAME will be backed up, please ensure any Data that is sitting in the root of D:\ is moved to D:\$env:USERNAME
FYI: Your desktop data is safe and will be backed up, you need only worry about data in the root of D:\ or anything you have stored in
C:\ Drive.
FURTHER MORE Outlook will shutdown by itself through this process, please do not open it up again until everything is finished.
If you have data to move, please click Cancel now, otherwise please press OK to continue the backup procedure.
For any help, please see your IT Technicians, or call
off-site.",
$WindowTitleWarning = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsWarning = [System.Windows.Forms.MessageBoxButtons]::OKCancel,
[System.Windows.Forms.MessageBoxIcon]$IconWarning = [System.Windows.Forms.MessageBoxIcon]::Stop
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageWarning, $WindowTitleWarning, $ButtonsWarning, $IconWarning)
}
#Checks to see if logged on user has a D:\USERNAME directory and if not, informs them to see IT for a custom backup.
Function TestUserDrive {
IF (!(Test-Path "D:\$env:USERNAME")){
#D:\USERNAME does NOT exist
}
}
#Displays an instruction/how-to of how to move data from the root of D:\ to the users D:\USERNAME folder
function WarningCancel(
$MessageCancel = "INSTRUCTIONS:
You have chosen to cancel the backup script, if this is due to requiring data to be moved to inside your D:\$env:USERNAME folder, please do the following.
1. Open My Computer
2. Double click on Data (D:) to open your D:\ Drive
3. Move or Copy anything from this directory that you wish to keep, into the directory called $env:USERNAME\My Documents
4. Once this has been completed, re-run this script to commence the backup procedure again
Easy as that!
For any help, please see your IT Technicians, or call
off-site",
$WindowTitleCancel = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsCancel = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]$IconCancel = [System.Windows.Forms.MessageBoxIcon]::Information
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageCancel, $WindowTitleCancel, $ButtonsCancel, $IconCancel)
}
#Informs the user to select the device they would like to backup to when the selection box is displayed
function SelectDevicePrompt(
$MessageSelect = "On the next screen please specify the device you would like to backup your data to.
The devices you currently have plugged in will show, please select your chosen device, and then click the OK button at the bottom right of the window.",
$WindowTitleSelect = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsSelect = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]$IconSelect = [System.Windows.Forms.MessageBoxIcon]::Hand
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageSelect, $WindowTitleSelect, $ButtonsSelect, $IconSelect)
}
#Displays a list of all removable storage devices volume names and their allocated drive letter
Function ShowUSBs{
$USBInfo = gwmi win32_diskdrive | ?{$_.interfacetype -eq "USB"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"}
$USBInfo | Format-Table `
#{Expression={$_.DeviceID};Label="Drive Letter";Width=25}, `
#{Expression={$_.VolumeName};Label="USB Name";Width=20}
}
#Allow user to select which drive to backup to and checks that the user hasn't canceled out of the Drive Selection GUI
Function SelectDrive{
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content $locBackupFolder\Drive.txt
IF ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
Exit
} ELSE {
$BackupDrive = $SelectedDrive.Substring(0,2)
}
}
$BackupDrive
#Imports list of active processes and looks for outlook process then kills it if found
function KillOutlook{
$processactive = Get-Process
IF($processactive.ProcessName -contains "Outlook") {
Stop-Process -Name Outlook
Start-Sleep 1
$OLcheckagain = Get-Process
IF($OLcheckagain.Processname -contains "Outlook") {
Write-Host "Outlook failed to close"
}
} Else {
Write-Host "Outlook is closed"
}
}
#Find the pst files attached to outlook and output the values to C:\Backup\PST.txt
function FindPSTs{
$outlook = New-Object -comObject Outlook.Application
$pstloc = $outlook.Session.Stores | where { ($_.FilePath -like '*.PST') }
$pstloc.FilePath | out-file -FilePath "$locBackupFolder\PST.txt" -Encoding Default
}
#Removes C:\Backup Directory
Function RemoveBackupDrive {
IF (Test-Path $locBackupFolder){
Remove-Item $locBackupFolder -Force -Recurse
}
}
#Copy data from D:\USERNAME to BackupDrive
Function CopyData {
IF (!(Test-Path $BackupDrive)) {
robocopy D:\$env:USERNAME $BackupDrive /MIR /COPYALL /r:03 /w:5 /MT:9
} ELSE {
robocopy D:\$env:USERNAME $BackupDrive /
}
}
#Copy PST files explicitly to BackupDrive\AppData\Roaming\Email
Function CopyPST {
KillOutlook
Start-Sleep 1
IF (!(Test-Path $BackupDrive\AppData\Roaming\Email)) {
New-Item $BackupDrive\AppData\Roaming\Email -ItemType Directory
}
Get-Content $locBackupFolder\PST.txt | ForEach-Object {
IF (Test-Path $_){
Copy-Item $_ "$BackupDrive\AppData\Roaming\Email"
}
}
}
######### START SCRIPT #########
#Display warning to inform user that only D:\USERNAME will be backed up
$WarningAccept = WarningDialog
#If cancel is selected from WarningDialog, then display WarningCancel message pop-up giving instructions to backup data from D:\ to D:\USERNAME
IF ($WarningAccept -eq "Cancel") {
WarningCancel
RemoveBackupDrive
Exit
}
#Prompts user to select Device to backup to
SelectDevicePrompt
#Shows the selection page for the user to select the device
SelectDrive
#Find the pst files attached to outlook and output to C:\Backup\PST.txt
FindPSTs
#Inform user where their data will be backed up to
Write-Host "Your data will be backed up to $BackupDrive"
#If Outlook is found, stop its process, otherwise continue
KillOutlook
#Running backup of everything in D:\ drive using robocopy
#If this is the first time copying all data /MIR will be used, otherwise if the directory DRIVE:\USERNAME\Backup exists
#robocopy will not /MIR and will only update newly added data.
CopyData
#Copy PST files specifically
#CopyPST
If I can be of any further assistance, please let me know.
I think your problem is simply that the SelectDrive function doesn't return anything. $backupDrive is local to the function, so you cannot refer to it from outside the function.
Also converting everything to text is going to introduce other bugs, e.g. you can select any line from the grid view including your headers and the blank lines top and bottom. Some better code might be:
Function GetUSBs{
$USBInfo = gwmi win32_diskdrive |
?{$_.interfacetype -eq "USB"} |
%{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} |
%{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"}
Write-Output $USBInfo | select #{n="Drive Letter";e={$_.DeviceID}},#{n="USB Name";e={$_.VolumeName}}
}
Function SelectDrive{
$usbs = GetUSBs
$usbs | Format-Table -AutoSize | Out-File $locBackupFolder\RemovableStorage.txt
$selectedDrive = $usbs | Out-GridView -OutputMode Single -Title "Choose USB Storage Device"
$selectedDrive | Out-File $locBackupFolder\Drive.txt -Encoding default
if ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
exit
} else {
write-output $selectedDrive.'Drive Letter'.Substring(0,2)
}
}
$backupDrive = SelectDrive
This version still writes to your two files, but it doesn't read either of them, instead it keeps the objects internally. This means you get a better display in the grid view as the grid's own column headings are used, and you don't get any selectable lines you don't want.
There's a lot going on in your script, I debugged through it myself and found that these lines are causing the problems
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content "$locBackupFolder\Drive.txt"
You need to remove this block of code:
Out-File $locBackupFolder\Drive.txt -Encoding default
It is creating a new blank Drive.txt file, after which, Get-Content is being called on that empty file.
Therefore, it is getting the content of a blank file, which will always result in the $SelectedDrive variable being set to null.
I've added content to my Drive.txt file after this block has been removed and the $SelectedDrive variable is set to the content of the file as expected
This construct won't work as you seem to expect:
Get-Content file | Out-GridView | Out-File otherfile
Out-GridView doesn't pass the data back into the pipeline for further processing. If you need data in both a grid view and a file you need to use the Tee-Object cmdlet before Out-GridView:
Get-Content file | Tee-Object otherfile | Out-GridView
In your case:
Get-Content "$locBackupFolder\RemovableStorage.txt" |
Tee-Object "$locBackupFolder\Drive.txt" |
Out-GridView -OutputMode Single -Title 'Choose USB Storage Device'
If you want the user to select a particular Item, Out-GridView won't work at all, because it's for output only (hence the verb Out). You'll need a form with a list box to allow the user to select an item:
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$frm = New-Object Windows.Forms.Form
$btn = New-Object Windows.Forms.Button
$btn.Location = New-Object Drawing.Size(10,220)
$btn.Size = New-Object Drawing.Size(75,23)
$btn.Text = "OK"
$btn.Add_Click({$frm.Close()})
$frm.Controls.Add($btn)
$list = New-Object Windows.Forms.ListBox
$list.Location = New-Object Drawing.Size(10,10)
$list.Size = New-Object Drawing.Size(260,40)
$list.Height = 200
Get-Content "$locBackupFolder\RemovableStorage.txt" | % {
$list.Items.Add($_)
} | Out-Null
$frm.Controls.Add($list)
$frm.Add_Shown({$frm.Activate()})
[void]$frm.ShowDialog()
$SelectedDrive = $list.SelectedItem
I am looking to convert all .doc documents that end up in a specific folder. This part I have covered (see below code). The issue that I am having is there is a field that is inside of the document itself that breaks every time you try to save the document as a different file type. If you highlight the field and lock it (CRTL + F11) then save works like a champ. If you have any ideas at all please let me know. I am willing to try anything out at this point in time.
$wdFormatPDF = 17
$word = New-Object -ComObject word.application
$word.visible = $false
$folderpath = "c:\test\*"
$fileTypes = "*.doc"
Get-ChildItem -path $folderpath -include $fileTypes |
foreach-object `
{
$path = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
"Converting $path to pdf ..."
$doc = $word.documents.open($_.fullname)
$doc.saveas([ref] $path, [ref]$wdFormatPDF)
$doc.close()
}
$word.Quit()
After you grab the filename and remove extension you didn't append .pdf to to it.
$path = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
"Converting $path to pdf ..."
You are missing this line
$path = $path + ".pdf"
## $doc = $word.documents.open($_.fullname)
$doc.saveas([ref] $path, [ref]$wdFormatPDF)
If that doesn't help then $doc has Fields.Locked Property which you set it to True or False.
$doc.Fields.Locked = $true
I have this powershell script running. The first time it runs it runs flawlessly, the second time it runs i get the error that the .csv cannont be access "because it is being used by another process. Any idea which part of the script is "holding onto" the file and how i can make it let it go at the end?
clear
set-executionpolicy remotesigned
# change this to the directory that the script is sitting in
cd d:\directory
#############################################
# Saves usernames/accountNumbers into array #
# and creates sql file for writing to #
#############################################
# This is the location of the text file containing accounts
$accountNumbers = (Get-Content input.txt) | Sort-Object
$accountID=0
$numAccounts = $accountNumbers.Count
$outString =$null
# the name of the sql file containing the query
$file = New-Item -ItemType file -name sql.sql -Force
###################################
# Load SqlServerProviderSnapin100 #
###################################
if (!(Get-PSSnapin | ?{$_.name -eq 'SqlServerProviderSnapin110'}))
{
if(Get-PSSnapin -registered | ?{$_.name -eq 'SqlServerProviderSnapin110'})
{
add-pssnapin SqlServerProviderSnapin100
Write-host SQL Server Provider Snapin Loaded
}
else
{
}
}
else
{
Write-host SQL Server Provider Snapin was already loaded
}
#################################
# Load SqlServerCmdletSnapin100 #
#################################
if (!(Get-PSSnapin | ?{$_.name -eq 'SqlServerCmdletSnapin100'}))
{
if(Get-PSSnapin -registered | ?{$_.name -eq 'SqlServerCmdletSnapin100'})
{
add-pssnapin SqlServerCmdletSnapin100
Write-host SQL Server Cmdlet Snapin Loaded
}
else
{
}
}
else
{
Write-host SQL Server CMDlet Snapin was already loaded
}
####################
# Create SQL query #
####################
# This part of the query is COMPLETELY static. What is put in here will not change. It will usually end with either % or '
$outString = "SELECT stuff FROM table LIKE '%"
# Statement ends at '. loop adds in "xxx' or like 'xxx"
IF ($numAccounts -gt 0)
{
For ($i =1; $i -le ($AccountNumbers.Count - 1); $i++)
{
$outString = $outstring + $AccountNumbers[$accountID]
$outString = $outString + "' OR ca.accountnumber LIKE '"
$accountID++
}
$outString = $outString + $AccountNumbers[$AccountNumbers.Count - 1]
}
else
{
$outString = $outString + $AccountNumbers
}
# This is the end of the query. This is also COMPLETELY static. usually starts with either % or '
$outString = $outString + "%'more sql stuff"
add-content $file $outString
Write-host Sql query dynamically written and saved to file
###########################
# Create CSV to email out #
###########################
#Make sure to point it to the correct input file (sql query made above) and correct output csv.
Invoke-Sqlcmd -ServerInstance instance -Database database -Username username -Password password -InputFile sql.sql | Export-Csv -Path output.csv
####################################
# Email the CSV to selected people #
####################################
$emailFrom = "to"
$emailTo = "from"
$subject = "test"
$body = "test"
$smtpServer = "server"
# Point this to the correct csv created above
$filename = "output.csv"
$att = new-object Net.mail.attachment($filename)
$msg = new-object net.mail.mailmessage
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.from = $emailFrom
$msg.to.add($emailto)
$msg.subject = $subject
$msg.body = $body
$msg.attachments.add($att)
$smtp.Send($msg)
Can you try to add at th end :
$att.Dispose()
$msg.Dispose()
$smtp.Dispose()
You could also try and use a tool like procmon and see what does the script do whenever it acquires a lock on the file and doesn't release it. Also, since (supposedly) the problem is with the .csv file, you could load it as byte array instead of passing it's path as an attachment. This way the file should be read once and not locked.