VBPrnDlg object incorrectly disabling page selection - vb6

I'm changing some crufty old printing code to use the Visual Basic Print Dialog Control to pass printer information to a CrystalReport object. It works great except for one thing - the page selection box is consistently disabled no matter what flags I pass the object. Here is my code:
Public Enum PrintDialogFlags
NoFlag = 0
DisablePagesButton = 1
LoadIntoPrnObject = 2
AutoStartPrint = 4
End Enum
Public Function ShowPrintDialogCR(ByVal hwnd As Long, ByRef cr As CrystalReport, _
Optional PrintFlags As Long = 0) As Boolean
//this function assumes cr is a valid report object
On Error GoTo ShowPrintDialogCR_Error
Dim PD As New vbprndlglib.PrinterDlg
//load default settings
PD.PrinterName = Printer.DeviceName
PD.DriverName = Printer.DriverName
PD.Port = Printer.Port
PD.PaperBin = Printer.PaperBin
PD.CancelError = True
PD.flags = (vbprndlglib.cdlPDNoSelection Or vbprndlglib.cdlPDHidePrintToFile)
// commented the line below out to see if it was something with my logic
// Still disables page selection without this line
//If PrintFlags And DisablePagesButton Then PD.flags = PD.flags Or vbprndlglib.cdlPDNoPageNums
Printer.TrackDefault = False
PD.ShowPrinter (hwnd)
cr.PrinterPort = PD.Port
cr.PrinterDriver = PD.DriverName
cr.PrinterName = PD.PrinterName
cr.CopiesToPrinter = PD.Copies
If PD.flags And vbprndlglib.cdlPDPageNums Then
cr.PrinterStartPage = PD.FromPage
cr.PrinterStopPage = PD.ToPage
End If
If PrintFlags And PrintDialogFlags.LoadIntoPrnObject Then
//copy these settings to the printer object
Dim prn As Printer
For Each prn In Printers
If prn.DeviceName = PD.PrinterName Then
Set Printer = prn
Exit For
End If
Next prn
Printer.PaperBin = PD.PaperBin
Printer.PaperSize = PD.PaperSize
Printer.Duplex = PD.Duplex
Printer.Copies = PD.Copies
Printer.ColorMode = PD.ColorMode
Printer.Orientation = PD.Orientation
Printer.PrintQuality = PD.PrintQuality
End If
Set PD = Nothing
If PrintFlags And PrintDialogFlags.AutoStartPrint Then cr.Action = 1
ShowPrintDialogCR = True
Printer.TrackDefault = True
Exit Function
ShowPrintDialogCR_Error:
If Err.Number = 20545 Then //request cancelled by user
MsgBox "The print request was cancelled after being submitted to the print spooler." & vbNewLine & _
"If you cancelled a print to file dialog, this is a normal message. " & vbNewLine & _
"Otherwise, this message could mean your printer is not accepting print requests from us at this time." _
, vbOKOnly + vbExclamation, "Print Request Cancelled"
ErrorLogger Err, "ShowPrintDialogCR"
ElseIf Err.Number <> 32755 Then
//something else besides clicking cancel, show the error
MsgBox "Error " & Err.Number & " - " & Err.Description & vbNewLine & "Source: " & _
Err.Source & vbNewLine & vbNewLine & "Document not printed.", vbOKOnly + vbCritical, "Print Failure"
ErrorLogger Err, "ShowPrintDialogCR"
End If
Err.Clear
ShowPrintDialogCR = False
Printer.TrackDefault = False
End Function
I don't see what I'm doing wrong here. I've passed several combinations of unrelated flags just to see if the box would enable itself with no success. I've encountered VB6 quirks before and I'm really hoping this isn't one of them. Any help is much appreciated!

I found this in the KB article you linked to:
To enable the Select Pages portion of
the Print dialog box, Max must be set
to a number that is larger than Min.
So, at the very least, you need to set the Min and Max properties on the print dialog object to something reasonable before you set the flags:
PD.CancelError = True
'Set Min and Max to enable page selection'
PD.Min = 1
PD.Max = 32767 'Or any large number really'
PD.flags = (vbprndlglib.cdlPDNoSelection Or vbprndlglib.cdlPDHidePrintToFile)
In my own experimenting I also found out the following few things:
If you just set Min and Max, the page selection will default both the "To" and "From" fields to 1.
If you set Min to 1 and Max to -1, and also set FromPage to 1 and ToPage to -1, the "From" field will default 1 and the "To" field will be empty. It's interesting that this works since the documentation states that Max must be larger than Min, but it looks like -1 is treated more like an "empty" or "null" value.
If you set the vbPrnDlg.cdlPageNums flag, the Print Dialog will default to the Pages radio button when it's displayed. If you omit the vbPrnDlg.cdlPageNums flag, the dialog will default to the All radio button.

Related

External incoming mail marked as draft and unsent

My question is similar to but not the same to the one below,
Mark a mailitem as sent (VBA outlook)
Basically, something (AV, bug in Outlook or Exchange or both), has modified hundreds of incoming (external emails) to a particular user as drafts and now appear as unsent. This means the user cannot reply to these messages and the suggested alternative of copying and pasting looks very unprofessional and confusing to the user's clients. Thankfully whatever was causing it stopped but the damage is done.
I need some way to modify the PR_MESSAGE_FLAGS programmatically. I am comfortable with VB script, VBA, VB.Net and even C#/C++ but I am coming up empty for how to do it.
Should it matter, the server is Exchange 2013 and client is Outlook 2010 or 2016 (32 or 64bit). The entire mailbox has been exported to PST and can be worked on offline if that helps. :)
Based on Dmitry's answer, here is the code that clones the old messages and marks them as sent so they can be replied to.
Only concern with it is that it seems to be breaking Conversations.
Dim mysession
Sub doFixDrafts()
log " Starting scan!"
Set mysession = CreateObject("Redemption.RDOSession")
mysession.Logon
Const sRootFolder = "\\Mailbox\Inbox"
Set oRootFolder = mysession.getfolderfrompath(sRootFolder)
'Set oRootFolder = mysession.PickFolder
doCleanupFolder oRootFolder, sRootFolder
log "Scan complete!!"
End Sub
Sub doCleanupFolder(oFolder, sFolder)
Dim c: c = 0
Dim i: i = 0
Dim tc: tc = Format(oFolder.Items.Count, "0000")
'Get start timestamp so we can report in at regular intervals...
Dim st: st = Now()
log "Checking... " & sFolder
Dim aMsgIDs()
'Make a list of 'unsent' messages
For Each Item In oFolder.Items
i = i + 1
If Not Item.Sent Then
c = c + 1
msgID = Item.EntryID
ReDim Preserve aMsgIDs(1 To c)
aMsgIDs(c) = msgID
c = Format(c, "0000")
End If
'Give update for large folders...
ct = Now()
td = DateDiff("s", st, ct)
If td > 15 Then
log c & "/" & i & "/" & tc & " so far..."
st = ct
End If
DoEvents
Next
c = Format(c, "0000")
log c & "," & tc & "," & sFolder
'Fix the corrupt messages
For m = 1 To CInt(c)
Set badMsg = mysession.GetMessageFromID(aMsgIDs(m))
sSender = badMsg.Sender
sSubject = badMsg.Subject
dSentDate = badMsg.SentOn
Set newMsg = oFolder.Items.Add("IPM.Note")
newMsg.Sent = True
badMsg.CopyTo (newMsg)
newMsg.Save
badMsg.Delete
Dim a As String
a = Format(m, "0000") & "," & sSender & ","
a = a & Chr(34) & sSubject & Chr(34) & ","
a = a & Chr(34) & dSentDate & Chr(34)
log a
DoEvents
Next m
For Each Item In oFolder.Folders
doCleanupFolder Item, sFolder & "\" & Item.Name
Next
End Sub
Sub log(s As String)
d = Format(Now(), "yyyy-mm-dd hh:mm:ss")
t = d & " " & s
Debug.Print t
Const logfile = "c:\temp\fixdrafts.txt"
Open logfile For Append As #1
Print #1, t
Close #1
End Sub
The answer is still the same - on the low (Extended MAPI) level, sent/unsent status (MSGFLAG_UNSENT bit in the PR_MESSAGE_FLAGS property) can only be changed before the item is saved for the very first time.
Outlook Object Model is subject to the same limitation of course, and the only way to create an item in the sent state is to create a PostItem object - it is created in the sent state. You will then need to change the message class back to IPM.Note and remove the icon related properties to make sure the item looks right.
Redemption (I am its author) lets you change the item's state (RDOMail.Sent is read/write before the first call to Save).
It should be pretty easy to create copies of existing unsent messages in the sent state - loop through the problematic messages (it is better to avoid using "for each" if you will be creating new items in the same folder - your "for each" loop will start picking up new messages. Loop through the messages first and store their entry ids in a list or array), create new item using Redemption (RDOFolder.Items.Add), set the Sent property to true (RDOMail.Sent = true), open the problematic message by its entry ids (RDOSession.GetMessageFromID), copy the problematic message into the new message using RDOMail.CopyTo(AnotherRDOMailObject), call RDOMail.Save on the new message and RDOMail.Delete on the old message.

MS Access 2016 File Browse Button Issues

I am using the script listed below (I honestly stole this probably from this very site) for a browse button on a form. The task is simply to start up MS File Dialog box so that a file (in this case an image file) can be selected. Once you select the record and click ok it then pastes the file name and location into a field.
Viewing the table the file name and location is pasted just as it should be. The problem comes in with a report I built. I have an image set to display with the control source linked back to that file address field. It will not display the image though.
However, if I manually type the same address character for character or even “copy”, delete, and then “paste” the same exact entry into the field the image then displays just fine on the report.
I have checked to make sure there are no spaces or characters anywhere there shouldn’t be. I am at a loss here.
Any help would be greatly appreciated and I will gladly give you my first born. Ok maybe not the first I like him but you can have the second one, she’s hell.
Private Sub Command67_Click()
On Error GoTo SubError
'Add "Microsoft Office 14.0 Object Library" in references
Const msoFileDialogFilePicker As Long = 3
'Dim FD As Office.FileDialog
Dim FDialog As Object
Dim varfile As Variant
Set FDialog = Application.FileDialog(msoFileDialogFilePicker)
EmployeePicture = ""
' Set up the File Dialog
Set FDialog = Application.FileDialog(msoFileDialogFilePicker)
With FDialog
.Title = "Choose the spreadsheet you would like to import"
.AllowMultiSelect = False
.InitialFileName = "C:\Users\" 'Folder picker needs trailing slash
.Filters.Clear
.Filters.Add "All", "*.*"
If .Show = True Then
If .SelectedItems.Count = 0 Then
'User clicked open but didn't select a file
GoTo SubExit
End If
'An option for MultiSelect = False
'varFile = .SelectedItems(1)
'EmployeePicture = varFile
'Needed when MultiSelect = True
For Each varfile In .SelectedItems
EmployeePicture = EmployeePicture & varfile & vbCrLf
Next
Else
'user cancelled dialog without choosing!
'Do you need to react?
End If
End With
SubExit:
On Error Resume Next
Set FDialog = Nothing
Exit Sub
SubError:
MsgBox "Error Number: " & Err.Number & " = " & Err.Description, vbCritical + vbOKOnly, _
"An error occurred"
GoTo SubExit
End Sub

add multiple multi string values to registry using array

Code is cleaned and changed from previous post since old logics had various errors that have been corrected and narrowed down to one error in one condition that I cant find an answer to. Currently getting error when my url is being read as only value and throwing Subscript Out of range error even though array is initialized. Other conditions when user has preset items or no key at all works perfectly. Thanks.
option explicit
'on error resume next
Dim ObjName,oADSysInfo,strComputer
Dim objReg,IE_Main,mstrValName,strFunctionIntranet,strNYHomepage,multiStringValues(),allURLs(),itemname,a,return
Set oADSysInfo = CreateObject("ADSystemInfo")
Set ObjName = GetObject("LDAP://" & oADSysInfo.UserName)
strComputer = "."
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
Const HKCU = &H80000001
IE_Main = "Software\Microsoft\Internet Explorer\Main"
mstrValName = "Secondary Start Pages"
strNYHomepage = "http://www.google.com"
strFunctionIntranet = "www.mycompany.com"
SetHomePage
Sub SetHomepage
objReg.setStringValue HKCU,IE_Main,"Start Page",strNYHomepage
'Reading MultiStringValue of "Secondary Start Pages" for HKCU and continuing if it has something preset.
return = objReg.getMultiStringValue (HKCU,IE_Main,mstrValName,multiStringValues)
If return=0 Then
a=0
'Reading all items currently set to make sure users retain their existing URLs.
For Each itemname In multiStringValues
'Only continue if any of the existing URLs DO NOT MATCH what we are enforcing as the URL.
If itemname <> strFunctionIntranet Then
WScript.Echo itemname
WScript.Echo "itemname is NOT equal intranet"
a = a + 1
ReDim Preserve allURLs(a)
allURLs(a) = itemname
'a = a + 1
End If
Next
objReg.DeleteValue HKCU,IE_Main,mstrValName
'Enforce our URL to always be the first item.
allURLs(0)=strFunctionIntranet
'Set the new MultiStringValue registry key back.
objReg.setMultiStringValue HKCU,IE_Main,mstrValName,allURLs
WScript.echo "finished setting all secondary tabs... "
Else
strFunctionIntranet = Array(strFunctionIntranet)
objReg.setMultiStringValue HKCU,IE_Main,mstrValName,strFunctionIntranet
End If
End Sub
Wscript.Quit
Your array contains an empty element, because you create it one field too big.
Change this line:
ReDim Preserve allURLs(a+1)
into this:
ReDim Preserve allURLs(a)

Warn before sending Outlook message

My Outlook Address book by default storing e-mail addresses in the combination of upper and lower case letters, in that case below code is not working for me. Please advise.
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim Recipients As Outlook.Recipients
Dim recip As Outlook.Recipient
Dim i
Dim prompt As String
On Error Resume Next
' use lower case for the address
' LCase converts all addresses in the To field to lower case
Checklist = "firstname.lastname#domain.com"
Set Recipients = Item.Recipients
For i = Recipients.Count To 1 Step -1
Set recip = Recipients.Item(i)
If InStr(LCase(recip), LCase(Checklist)) Then
prompt$ = "You sending this to this messgae to Treasurer " & Item.To & ". Are you sure you want to send it?"
If MsgBox(prompt$, vbYesNo + vbQuestion + vbMsgBoxSetForeground, "Check Address") = vbNo Then
Cancel = True
End If
End If
Next i
End Sub

Error code 0x8000500D when trying to access PasswordLastChanged

I'm writing a VBScript that will simply check each user in AD if their password has been changed within a given number of days. When I was trying to get it working for a single user, I came up with the following working code:
Option Explicit
Dim objUser, strLDAPConnection, intPwdExpLimit
strLDAPConnection = "CN=Test User,OU=Test,OU=Employees,DC=domain,DC=com"
intPwdExpLimit = 90
Set objUser = GetObject("LDAP://" + strLDAPConnection)
WScript.Echo DaysSincePwdChange(objUser)
Function DaysSincePwdChange(objUserAccount)
DaysSincePwdChange = dateDiff("d", objUserAccount.PasswordLastChanged, Now)
End Function
So then I tried to get it to work by looping through all users in a Test OU with the following code:
Option Explicit
Const strOffice = "Test"
Dim objEmployeesOU, objUser, intPwdExpLimit
intPwdExpLimit = 90
Set objEmployeesOU = GetObject("LDAP://OU=" & strOffice & _
",OU=Employees,DC=domain,DC=com")
For Each objUser In objEmployeesOU
If objUser.class = "user" Then
If ((DaysSincePwdChange(objUser)) >= intPwdExpLimit) Then
MsgBox(objUser & ": Password Expired.")
Else
MsgBox(objUser & ": Password Current.")
End If
End If
Next
Function DaysSincePwdChange(objUserAccount)
DaysSincePwdChange = dateDiff("d", objUserAccount.PasswordLastChanged, Now)
End Function
The above code produces a 0x8000500D error and googling the error says that it can't find the property in the cache (referring to the PasswordLastSet property, see error description link here).
Any ideas why the first block of code works fine but the second has a problem accessing that property?
Error code 0x8000500d means E_ADS_PROPERTY_NOT_FOUND. The password of the user has never been changed, so the property is not set. You could handle the condition like this:
Function DaysSincePwdChange(objUserAccount)
On Error Resume Next
DaysSincePwdChange = dateDiff("d", objUserAccount.PasswordLastChanged, Now)
If Err Then
If Err.Number = &h8000500d Then
DaysSincePwdChange = -1
Else
WScript.Echo "Unexpected Error (0x" & Hex(Err.Number) & "): " & _
Err.Description
WScript.Quit 1
End If
End If
End Function
and modify the check like this:
passwordAge = DaysSincePwdChange(objUser)
If passwordAge >= intPwdExpLimit) Then
MsgBox(objUser & ": Password Expired.")
ElseIf passwordAge = -1 Then
MsgBox(objUser & ": Password never changed.")
Else
MsgBox(objUser & ": Password Current.")
End If

Resources