Solved

Disabling "Print" menu of any specific application

Posted on 2004-04-05
38
2,235 Views
Last Modified: 2007-12-19
Hi,
Xperts, please pay your kind attentions to my requirements as follows -
1. I want to disable "Print" related menus in any application currently running like Word, Xcel or Opera or IE... It can be any WINDOWS based app.
2. I dont want support for DOS apps.
3. I should be able to select specific app[from installation list or what ever you suggest] and disable "Print" related functions of that.
4. Other apps should work normally in this case.
5. I should be able to reenable it as and when i want.
6. If you think a app should be kep running in background, then i want it to be running as service & no one should be able to stop or end that app using task manager.

To give details, I m producing a demo of app to a commercial organisation. So if that succeeds & if i get work, I will like to pay a part of what so ever amount I get from that project, where i might have some related queries, to be solved by successful xpert. Looking for real long term relationship. Feel free to contact me on my email also... its vinay@mimansa.net

If you want points, let me know ur requirement, I will purchase it and give u that... I dont want to be stingy if i m getting my purpose sorted out! But as i have only 80 points left i m assigning all that.... Regards

Sincerely.
Vinay Samant.
0
Comment
Question by:vinaysamant
  • 21
  • 15
  • 2
38 Comments
 

Author Comment

by:vinaysamant
ID: 10781974
Hi,
This is special comment for moderator...

I think there are no more Experts in experts exchange. You say that, you are the #1 place for experts, then where are they? And in this case why should i think of Premium services?

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10788387
Hi
IMHO, there are 2 ways to disable menu item
1. Monitoring windows creation (see my sample at http://www.freevbcode.com/ShowCode.Asp?ID=1308 ) and delete menu (API DeleteMenu) or disable menu (API EnableMenuItem). In this case your app should run at start up and main form should have Cancel = True in Form_Unload event.
2. Under Windows NT you can modify application resource to remove special menu item.
0
 

Author Comment

by:vinaysamant
ID: 10788479
Hi Ark,
you have helped me out twice. Please share ur email so that we can keep in touch. If i get this or any similar project, i will certainly like to share rewards with u. Let me try ur solution!

by the way IMHO means what?

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10788509
IMHO = In My Humble Opinion (http://info.astrian.net/jargon/terms/i/IMHO.html)
0
 

Author Comment

by:vinaysamant
ID: 10788666
lol
0
 

Author Comment

by:vinaysamant
ID: 10793684
Ark Ark Ark,
i am trying your app.... but 1 small doubt... very small for you. As you given way to disable any menu item, and no one will be able to click that, but what if some one is using Ctrl+P or any other direct printing hotkey or any other mean! Means, as we can monitor window creation, can we monitor printer job send to printer by any application, and show a popup asking "do you want to block this" & block that popup for ever. Last part i will do but how to monitor job send to printer & kill it? Please guide. I think we can get that project because this was the last question asked!

With best wishes & regards,
Vinay Samant.
0
 

Author Comment

by:vinaysamant
ID: 10793708
Hi Ark,
I m increasing your points... 2 the max i have! If you want more then let me know. I will purchase them for you!

Vinay Samant.
0
 

Author Comment

by:vinaysamant
ID: 10796378
Ark,
Why are you not replying? I tried you app and can trace all windows. But when it comes to retriving menus, menus from ms word, excel cant be retrieved. Can u tell me the reason?

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10802439
Hello Vinay!
Sorry for silence - I was absent all weekend. Yes, I encountered same problem when trying to make sample with IE, though samples with some other apps works fine. The reason of this prb is following - IE, new Office, 2000/XP explorer and some other applications use new Toolbar instead of menu. I'll try resolve this problem as well as monitoring printing job (BTW, IMHO thisis a good idea).

Here is my attempts:
'================Bas module================
Private Type MenuItem
    hMenu As Long
    wID As Long
    wIDType As Long
End Type

Private Const MF_BYCOMMAND = &H0&
Private Const MF_BYPOSITION = &H400&

Private Const MF_ENABLED = &H0&
Private Const MF_GRAYED = &H1&
Private Const MF_DISABLED = &H2&

Private Declare Function GetMenu Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As Long, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
Private Declare Function GetSubMenu Lib "user32" (ByVal hMenu As Long, ByVal nPos As Long) As Long
Private Declare Function GetMenuItemID Lib "user32" (ByVal hMenu As Long, ByVal nPos As Long) As Long
Private Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long
Private Declare Function EnableMenuItem Lib "user32" (ByVal hMenu As Long, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long

Private Declare Function GetClassLong Lib "user32" Alias "GetClassLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function GetModuleFileName Lib "Kernel32" Alias "GetModuleFileNameA" (ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function EnumWindows& Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long)
Private Declare Function IsWindowVisible& Lib "user32" (ByVal hWnd As Long)
Private Declare Function GetParent& Lib "user32" (ByVal hWnd As Long)
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function GetWindowWord Lib "user32" (ByVal hWnd As Long, ByVal nIndex As Long) As Integer

Private Const GCL_HMODULE = (-16)
Private Const GWL_HINSTANCE = (-6)

Public sCaption As String
Public sClassName As String
Public sPrintMenuName As String
Public sAccelerator As String

Public Function TogglePrintMenu(ByVal hWnd As Long, ByVal bEnable As Boolean)
   Dim hMenu As Long, wID As Long
   Dim mask As Long, key As Long
   Dim sAcc
   Dim n As Long, i As Long, f As Long
   Dim mi As MenuItem
   
   hMenu = GetMenu(hWnd)
   mi.hMenu = hMenu
'   If hMenu = 0 Then Exit Function
   If sPrintMenuName <> "" Then
      Call FindMenuIDByName(hMenu, sPrintMenuName, mi)
   End If
   If mi.wID = 0 And sAccelerator <> "" Then
      sAcc = Split(sAccelerator, "+")
      n = UBound(sAcc)
      For i = 0 To n - 1
          Select Case UCase(sAcc(i))
              Case "SHIFT": mask = mask + vbShiftMask
              Case "ALT":   mask = mask + vbAltMask
              Case "CTRL":  mask = mask + vbCtrlMask
          End Select
      Next i
      key = Asc(sAcc(n))
      mi.wID = GetMenuByShortcut(hWnd, mask, key)
      mi.wIDType = MF_BYCOMMAND
   End If
   If mi.wID = 0 Then Exit Function
   If bEnable Then f = MF_ENABLED Else f = MF_GRAYED 'Or MF_DISABLED
   EnableMenuItem mi.hMenu, mi.wID, f Or mi.wIDType
End Function

Public Function FindMenuIDByName(ByVal hMenu As Long, ByVal sName As String, mi As MenuItem)
   Dim num As Long, i As Long
   Dim sub_menu As Long, length As Long
   Dim mnu_name As String
   Dim cmdID As Long
   
   If hMenu = 0 Then Exit Function
   sName = Replace(sName, "&", "")
   num = GetMenuItemCount(hMenu)
   For i = 0 To num - 1
        sub_menu = GetSubMenu(hMenu, i)
        mnu_name = Space$(256)
        length = GetMenuString(hMenu, i, mnu_name, Len(mnu_name), MF_BYPOSITION)
        mnu_name = Left$(mnu_name, length)
        mnu_name = Replace(mnu_name, "&", "")
        If UCase(mnu_name) = UCase(sName) Then
           cmdID = GetMenuItemID(hMenu, i)
           If cmdID = -1 Then  'Item ID
              mi.hMenu = hMenu
              mi.wID = i
              mi.wIDType = MF_BYPOSITION
           Else 'Command ID
              mi.wID = cmdID
              mi.wIDType = MF_BYCOMMAND
           End If
           Exit For
        End If
        FindMenuIDByName sub_menu, sName, mi
   Next i
End Function

'Don't work as expected
Public Function GetMenuByShortcut(ByVal hWnd As Long, ByVal mask As ShiftConstants, ByVal key As KeyCodeConstants) As Long
   Dim hMod As Long
   Dim sName As String
   sName = GetModulePathName(hWnd)
   Debug.Print sName
End Function

'Don't work as expected
Public Function GetModulePathName(ByVal hWnd As Long) As String
  Dim sName As String
  Dim ret As Long
  Dim hMod As Long
  sName = Space(260)
  hMod = GetClassLong(hWnd, GCL_HMODULE)
  ret = GetModuleFileName(hMod, sName, Len(sName))
  If ret > 0 Then GetModulePathName = Left$(sName, ret)
End Function

Public Function GetWndText(ByVal hWnd As Long) As String
  Dim k As Long, sName As String
  sName = Space$(128)
  k = GetWindowText(hWnd, sName, 128)
  If k > 0 Then sName = Left$(sName, k) Else sName = "No caption"
  GetWndText = sName
End Function

Public Function GetWndClass(ByVal hWnd As Long) As String
  Dim k As Long, sName As String
  sName = Space$(128)
  k = GetClassName(hWnd, sName, 128)
  If k > 0 Then sName = Left$(sName, k) Else sName = "No class"
  GetWndClass = sName
End Function

Function EnumWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
  Dim sClass As String, sName As String
  Dim bFind As Boolean
  If IsWindowVisible(hWnd) And GetParent(hWnd) = 0 Then
     If sCaption <> "" Then
        sName = GetWndText(hWnd)
        If InStr(1, UCase(sName), UCase(sCaption)) Then
           bFind = True
        End If
     End If
     If sClassName <> "" Then
        sClass = GetWndClass(hWnd)
        If UCase(sClass) = UCase(sClassName) Then bFind = True Else bFind = False
     End If
     If bFind Then
        TogglePrintMenu hWnd, CBool(lParam)
     End If
  End If
  EnumWinProc = 1
End Function

Public Sub ToggleExisting(ByVal bEnable As Boolean)
   EnumWindows AddressOf EnumWinProc, bEnable
End Sub

'=============Form code===========

Dim WithEvents SH As cShellHook
Dim bEnable As Boolean

Private Sub Command1_Click()
   bEnable = Not bEnable
   ToggleExisting bEnable
End Sub

Private Sub Form_Load()
'   Label1 = "Press button to disable/enable print menus in all IE windows"
'   sCaption = "Microsoft Internet Explorer"
   sCaption = "Total Commander"
'   sClassName = "IEFrame"
'   sAccelerator = "Ctrl+P"
   sPrintMenuName = "Ïå÷àòü" ' Print Menu Locale caption
   Command1.Caption = "Disable menu"
   bEnable = True
   Set SH = New cShellHook
   SH.SetShellHook Me.hWnd, RSH_REGISTER_TASKMAN
End Sub

Private Sub Form_Unload(Cancel As Integer)
   ret = MsgBox("Do you realy want close me?", vbExclamation + vbYesNo)
   If ret = vbNo Then
      Cancel = True
   Else
      SH.RemoveShellHook
      Set SH = Nothing
   End If
End Sub

Private Sub SH_WindowCreated(ByVal hWnd As Long)
   TogglePrinMenu hwnd, bEnable
End Sub

PS. Don't worry about points, I already have enough :)
0
 
LVL 27

Expert Comment

by:Ark
ID: 10802456
0
 

Author Comment

by:vinaysamant
ID: 10803701
Hi,
Ark listen to a very easy concept. Forget disabling menu as we have to keep struggling between many possibilities. aim was <b>Disabling PRINT from any specified application</b> The steps are very simple now. Lets move as follows -
1. Trapping opening of any application. Getting its handle, caption & listing its occurance.<b>Completed</b>

2. Trapping printing jobs. See the code which traps all printing jobs using EnumJobs -

Now here we just want to find out, which job has been posted by which application. Means either handle or caption of posting application! IF we can get this then our job is to just kill this printing job <b>Not Completed</b>

3. One more simple way to achieve all this is trapping mouse clicks. See it is possible to have global mouse hook the way u have global windows hook. If we can get the Text or Caption of the placed where mouse is recently clicked with caption of parent application, and if it matches with our restricted application then just disabling further print activity or removing latest print job! <b>Not completed</b>
If we can achieve any one of 2 or 3 then we are done. I think 2 is easier. I am also trying the same!

Vinay Samant.
0
 

Author Comment

by:vinaysamant
ID: 10803751
Sorry sorry sorry,
i forgot to give u the link -
http://www.vbcity.com/download/PrintSpool.zip

vin
0
 
LVL 27

Accepted Solution

by:
Ark earned 100 total points
ID: 10803773
Thanks, I know this link (see accepted answer from http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_20875270.html) :)
Unfortunatelly, I lost this code and my proxy currently don't allow me downloading anything from the web (except of html pages).
0
 

Author Comment

by:vinaysamant
ID: 10804301
Hi Ark,
I m using the same link and downloaded the code & used it. It monitor the jobs well but is there any way to find out which application has posted this job??? if this can be done then only its of any use. Because every application is giving different styles name to app. So no link between appname & printjobname exist. We need to get the handle of application which posted this job for printing.... Can u please guide me on that?

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10804328
Sorry, it's too late today, continue tomorrow.
Take a look on GetJob API, in particular at JOB_INFO_1.pDocument
0
 

Author Comment

by:vinaysamant
ID: 10805539
hi Ark,
I seen that API visiting microsoft site also. It just returns the name of document its printing. In case of IE, it returns the name of URL stupidly & in case of word & xcel it returns the name of document. In case of opera it returns heading of page. So it used many nonstandard practicees to return the document name. Is there any way where we will be able to find the exact caption of the application which send this job for printing?


Or can we do something like this -
1. LEts declare a global mouse hook. It will trap all cliks of mouse.
2. It will trap the caption where ever mouse is clicked. If it is Print then it will stop activity initiated by that click. Or it will kill the latest printjob at that time. IS this possible?


Cant we really think of disable every thing in certain app which has caption like Print.


Or can we disable the way applications print to printer?

Let me know please.
Please find time to finish this. My demo cant be presented without that!

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10809348
Unfortunatelly, user can use hot key (like Ctrl+P) for printing without the mouse. IMHO to get 100 proff result we need experiment with document names for particular application (probably compare pDocument with EnumWindows/GetWindowText).
0
 

Author Comment

by:vinaysamant
ID: 10810836
Hi Ark,
I tried using getwindow text & comparing it with printjobs but it seems to be hopeless. See in case of word the job name will be Microsoft Word - Document1.doc. In case of xcel it will be just book1.xls. In case of opera just caption of window. But in case of IE its name of url!!! Now in this fasion if every app will give different name to printjob then how will we find the posting application?

I have 2nd thought - Can we block printer port itself so that every time any application whoever want to print has to send us message insted of sending message to printer Q. Is it possible? In you yesterday's link there is a page which shows how to get notified as soon as printer gets a job[i dint understood hell of that ;)] In that case, as soon as we get notified that some one trying to print, can we use enumwindows to see who is printing? The link si below -

http://www.merrioncomputing.com/Programming/WatchPrinter.htm

Plese help me out my friend. If you know, you are the only one all over web who has atleast this much of knowledge. Otherwise i am not able to find any more resource online for this problem!

Thanks a lot to you!
Vinay SAmant
0
 
LVL 27

Expert Comment

by:Ark
ID: 10811441
Hi
I'm afraid, if JOB_INFO haven't appropriate field, we can not retrive this information in plain way. As you can see from merrion sample, all thouse function are from winspool.drv. This is how windows printing works: applications send request to spooler to set document into queue -> spooler inform all other applications (with WM_SPOOLERSTATUS or FindFirst/Next...) about spooler satus so applications can respond on these notifications (delay own print job, cancel one/all jobs etc.). But spooler sends only PREDEFINED info and there is no way to force it send more info. I supposed there is some undocumented way (probably with the most powerfull as well as least undocumented NtQueryInformation API), but I'm not sure it's possible from VB since this (and same) functions use pointers heavelly and VB is not propr language to deal with pointers. You can also try WMI:

Private Sub Command1_Click()
On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_PrintJob", , 48)
For Each objItem In colItems
    Debug.Print "Caption: " & objItem.Caption
    Debug.Print "DataType: " & objItem.DataType
    Debug.Print "Description: " & objItem.Description
    Debug.Print "Document: " & objItem.Document
    Debug.Print "DriverName: " & objItem.DriverName
    Debug.Print "ElapsedTime: " & objItem.ElapsedTime
    Debug.Print "HostPrintQueue: " & objItem.HostPrintQueue
    Debug.Print "InstallDate: " & objItem.InstallDate
    Debug.Print "JobId: " & objItem.JobId
    Debug.Print "JobStatus: " & objItem.JobStatus
    Debug.Print "Name: " & objItem.Name
    Debug.Print "Notify: " & objItem.Notify
    Debug.Print "Owner: " & objItem.Owner
    Debug.Print "PagesPrinted: " & objItem.PagesPrinted
    Debug.Print "Parameters: " & objItem.Parameters
    Debug.Print "PrintProcessor: " & objItem.PrintProcessor
    Debug.Print "Priority: " & objItem.Priority
    Debug.Print "Size: " & objItem.Size
    Debug.Print "StartTime: " & objItem.StartTime
    Debug.Print "Status: " & objItem.Status
    Debug.Print "StatusMask: " & objItem.StatusMask
    Debug.Print "TimeSubmitted: " & objItem.TimeSubmitted
    Debug.Print "TotalPages: " & objItem.TotalPages
    Debug.Print "UntilTime: " & objItem.UntilTime
Next

End Sub

Regards
Ark

PS. Just got an idea - another approach can be use - we can trap WindowActivated event from my system shell hook, then get this window caption - if caption match restricted list - set some public boolean variable to True and False otherwise. In monitoring function (either with WM_SPOOLERSTATUS or FindFirst/NextPrinterNotification) allow or cancel any job according to this flag.
0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 

Author Comment

by:vinaysamant
ID: 10811517
yah but how u know which job is submitted by which window? There is no corelation or hardly any standard relation between windows captions & the names of documents submitted by them. For eg. in case of IE, the window caption is title of html page while name of document submitted to print is url in addressbar! In case of word it attached Microsoft Word with every document name! And in this way every document has different formats to submit names of document for printing!

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10811596
Check oput my las idea:
1. You can start printing from active window only (either from menu or shortcut)!
My ShellHook ( http://www.freevbcode.com/ShowCode.Asp?ID=1308 ) allow trap activation event and return windows handle. Knowing windows handle, you can get any information you want (caption, class name, process/thread id, executable owned this window etc.). Now you can compare currently active window (by caption, class name, executable) with restricted list and set bCancel flag to True, if this window is in restricted list.
2. Now, using http://www.vbcity.com/download/PrintSpool.zip, you get spooler notification: new job appear - and you know - it's from active window, so just use SetJob API with this JobID to cancel printing.
Alternatevely, you can use GetForegroundWindow API at the moment you receive notification to determine which window is active now and send this job.
Here is a code to get executable path from windows handle (note: works on all platforms except of NT4 - NT4 use psapi.dll for this - if you need code for psapi.dll, let me know)

'===========bas module code=============
Public Const TH32CS_SNAPPROCESS As Long = 2&
Public Const MAX_PATH As Long = 260

Public Type PROCESSENTRY32
    dwSize As Long
    cntUsage As Long
    th32ProcessID As Long
    th32DefaultHeapID As Long
    th32ModuleID As Long
    cntThreads As Long
    th32ParentProcessID As Long
    pcPriClassBase As Long
    dwflags As Long
    szexeFile As String * MAX_PATH
End Type

Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long,
lpdwProcessId As Long) As Long
Public Declare Function CreateToolhelpSnapshot Lib "Kernel32" Alias
"CreateToolhelp32Snapshot" (ByVal lFlgas As Long, ByVal lProcessID As Long) As Long
Public Declare Function ProcessFirst Lib "Kernel32" Alias "Process32First" (ByVal
hSnapshot As Long, uProcess As PROCESSENTRY32) As Long
Public Declare Function ProcessNext Lib "Kernel32" Alias "Process32Next" (ByVal
hSnapshot As Long, uProcess As PROCESSENTRY32) As Long
Public Declare Sub CloseHandle Lib "Kernel32" (ByVal hPass As Long)

Public Function GetExeFromHandle(hWnd As Long) As String
    Dim threadID As Long, processID As Long, hSnapshot As Long
    Dim uProcess As PROCESSENTRY32, rProcessFound As Long
    Dim i As Integer, szExename As String
   
    ' Get ID for window thread
    threadID = GetWindowThreadProcessId(hWnd, processID)
   
    ' Check if valid
    If threadID = 0 Or processID = 0 Then Exit Function
   
    ' Create snapshot of current processes
    hSnapshot = CreateToolhelpSnapshot(TH32CS_SNAPPROCESS, 0&)
   
    ' Check if snapshot is valid
    If hSnapshot = -1 Then Exit Function
   
    'Initialize uProcess with correct size
    uProcess.dwSize = Len(uProcess)
       
    'Start looping through processes
    rProcessFound = ProcessFirst(hSnapshot, uProcess)
    Do While rProcessFound
        If uProcess.th32ProcessID = processID Then
            'Found it, now get name of exefile
            i = InStr(1, uProcess.szexeFile, Chr(0))
            If i > 0 Then szExename = Left$(uProcess.szexeFile, i - 1)
            Exit Do
        Else
            'Wrong ID, so continue looping
            rProcessFound = ProcessNext(hSnapshot, uProcess)
        End If
    Loop
    Call CloseHandle(hSnapshot)
    GetExeFromHandle = szExename
End Function

'========Using========
'Just call
Debug.print GetExeFromHandle(Me.hWnd)
0
 

Author Comment

by:vinaysamant
ID: 10811651
Hi,
This idea is cool! But what if a print job appears through lan... and at same moment the activ window is restricted one? Our app will kill the network printing job. I think we can check the machine name in this case isnt it?

Can you please do me a favor? I dint understand a single line of the code written for printer notification on

http://www.merrioncomputing.com/Programming/WatchPrinter.htm

Can u please provide me with code which monitors just submitted printing jobs, list them with their properties like document name, machinename & time of submission & kills them if some one press kill button! I t will be great favor of you! I already developed  code which allows tracing windows with changing captions & finding out latest active window! That is working totally fine.... I just need above monitoring code.... I like that idea of getting notified whenever printer q activates insted of using timer & enumjobs to get jobs. It will also conserve resources isnt it?

Vinay Samant
0
 
LVL 27

Expert Comment

by:Ark
ID: 10811719
Hi
I suggest you to use WM_SPOOLERSTATUS message notification instead of FindFirstPrinterChangeNotification. If you read article carefully, you noticed that FindFirst wait notification asyncroneously, ie your app freeze until event occure:
'Cut from merrion article:
========================8<====================
This will wait forever, unless a printer event occurs. Note that because Visual Basic is a single threaded application no user interface updates can take place while the wait is active.
========================8<====================
Multithreading and/or 2 apps communications are not simple task in VB.
In this case subclassing main window like printspool.zip does is much better. But unfortunatelly, I can not download this file to make sample for you. Can you send this zip file via e-mail (ark@msun.ru).

PS. I have to finish today. Continue tomorrow.
0
 
LVL 2

Expert Comment

by:Merrion
ID: 10817860
FindFirstPrinterChangeNotification does not lock the application - it is WaitForSingleObject and then only because no timeout is specified.  It is possible to call WaitForSingleOption with a relatively small timeout and thus have the application run even though no printer events occur.  Suggest a look at the MSDN information on WaitForSingleObject API call.
0
 

Author Comment

by:vinaysamant
ID: 10818711
Hi Merian & Ark,
Most of my objectives are solved. I just need 1 small help. A piece of code [actual code steps] which uses FindFirstPrinterChangeNotification & WaitForSingleObject. This code should just say msgbox "Job Submitted" with document name machine name, author name & timestamp of job submission whenever a job gets submitted for printing! thats all i want.... Rest of the things i will take care of.

My application is about to finish & waiting for this piece of code only! I will be greatful to you if you provide me with that. I m really dumb at API calls!

Vinay Samant
0
 
LVL 27

Expert Comment

by:Ark
ID: 10819222
Hi Merrion
If we set timeout other then infinite, we have to make a loop, wich lead to eating of machine resources. IMHO subclassing is better.
PS. The correct approach should be 2 threads, one watching spooler, one for main app, but multithreading in VB ..... (insert your opinion).
0
 

Author Comment

by:vinaysamant
ID: 10820634
Hello,
will you people please pay a little attention to me? Actually my entire app is ready which monitors all apps[courtsey of Ark] Then whenever someone restricts some type of app [Say microsoftword where the class is Opus] then it restricts all windows with that class & enters handles of all those windows into registry...... This is working perfectly fine. Y dont you people please help me out in developing a seperate application which will
1. wait for printer event to happen exactly like merian.
2. Verifies that job is from local machine![very imp to avoid LAN printing jobs]
2. then take handle of foreground window [getforegroundwindow] & see if its within restricted windows in registry[I will write this step as i know where i have stored my data & in which way!]
3. If so kills the job!
4. waits for next printer event to happen!
5. Do you se any problem in this functionality
What else you need?
6. I also want to make this application invisible in TASk Manager [not in taskbar]... How to do it? Is there any way to make this invisible from Task Manager?

Please you people find time to give me this code as all my requirements are going to get solved out of this! I \wont need any more help after this & i wont bug you people any more! But please help me out... Afterall demo has to be submitted within time isnt it? Other what's the use? I will loose the project otherwise.

Vinay Samant.
0
 
LVL 2

Expert Comment

by:Merrion
ID: 10820897
Go to http://groups.yahoo.com/group/MerrionComputing/files/ (you will need to register) and download PrintWatchServer.zip and PrintWatchClient.zip - this shows the FindFirstPrinterChangeNotification stuff plus inter process communication.  You should be able to do the rest yourself.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10828825
Hi Vinay!
One last important question - your app will be placed at computer connected to printer via lpt/usb directly or it can be place at computer connected to printer via LAN? This is important because in second case your app won't receive WM_SPOOLERSTATUS message and FindFirstPrinterChangeNotification is the only way.
0
 

Author Comment

by:vinaysamant
ID: 10833030
Hi Ark,
you will like to know that app is working cooly no probs & i managed to use findfirst.... api ofcourse all due to you! Actually i am posting the code below which kills all jobs in default printer q! Dont think much about foreground window as i will manage it. I am already working on that! Just help me out in few thing if you can -
It uses a function        
Call WaitForSingleObject(mEventHandle, INFINITE)
This waits infinite right! There is function which can wait for many objects at the same time using array of hEventHandle i hope. ITs there on the msdn website. Can we use it so that we can monitor all printers in same application?

Another thing is we are using aWord array to retrieve properties of job.[thats what i understood] I can retrieve jobid and kill it. But can you explain me how to retrieve other information of any job using jobid?

And as i am using your shellhook.dll, the example specifies about getting text & classname for any running window. Can we get exename also of running window? This is very very importent.

Also when my program is running, no one should be able to kill that. I used idea of Cancel = 1. But still taskmanager manages to kill that! Help me out here. This is vital part.

The code is -
'--------------------------------------------------
Option Explicit

Public Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, pDefault As Any) As Long
Public Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Public Declare Function EnumJobs Lib "winspool.drv" Alias "EnumJobsA" (ByVal hPrinter As Long, ByVal FirstJob As Long, ByVal NoJobs As Long, ByVal Level As Long, pJob As Any, ByVal cdBuf As Long, pcbNeeded As Long, pcReturned As Long) As Long
Public Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Public Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" (lpString1 As Any, lpString2 As Any) As Long

Public Declare Function FindFirstPrinterChangeNotificationLong Lib "winspool.drv" Alias "FindFirstPrinterChangeNotification" (ByVal hPrinter As Long, ByVal fdwFlags As Long, ByVal fdwOptions As Long, ByVal lpPrinterNotifyOptions As Long) As Long
Public Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Public Declare Function FindNextPrinterChangeNotificationByLong Lib "winspool.drv" Alias "FindNextPrinterChangeNotification" (ByVal hChange As Long, pdwChange As Long, pPrinterOptions As PRINTER_NOTIFY_OPTIONS, ppPrinterNotifyInfo As Long) As Long
Public Declare Function FreePrinterNotifyInfoByLong Lib "winspool.drv" Alias "FreePrinterNotifyInfo" (ByVal pInfo As Long) As Long
Public Declare Sub CopyMemoryPRINTER_NOTIFY_INFO Lib "kernel32" Alias "RtlMoveMemory" (Destination As PRINTER_NOTIFY_INFO, ByVal Source As Long, ByVal Length As Long)
Public Declare Sub CopyMemoryPRINTER_NOTIFY_INFO_DATA Lib "kernel32" Alias "RtlMoveMemory" (Destination As PRINTER_NOTIFY_INFO_DATA, ByVal Source As Long, ByVal Length As Long)
Public Declare Function SetJob Lib "winspool.drv" Alias "SetJobA" (ByVal hPrinter As Long, ByVal JobId As Long, ByVal Level As Long, pJob As Byte, ByVal Command As Long) As Long
Public Declare Function GetJob Lib "winspool.drv" Alias "GetJobA" (ByVal hPrinter As Long, ByVal JobId As Long, ByVal Level As Long, pJob As Byte, ByVal cdBuf As Long, pcbNeeded As Long) As Long

Public Declare Function IsBadStringPtrByLong Lib "kernel32" Alias "IsBadStringPtrA" (ByVal lpsz As Long, ByVal ucchMax As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Public Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long

Public Declare Function GetForegroundWindow Lib "user32" () As Long

Public Enum PrinterChangeNotifications
    PRINTER_CHANGE_ADD_PRINTER = &H1
    PRINTER_CHANGE_SET_PRINTER = &H2
    PRINTER_CHANGE_DELETE_PRINTER = &H4
    PRINTER_CHANGE_FAILED_CONNECTION_PRINTER = &H8
    PRINTER_CHANGE_PRINTER = &HFF
    PRINTER_CHANGE_ADD_JOB = &H100
    PRINTER_CHANGE_SET_JOB = &H200
    PRINTER_CHANGE_DELETE_JOB = &H400
    PRINTER_CHANGE_WRITE_JOB = &H800
    PRINTER_CHANGE_JOB = &HFF00
    PRINTER_CHANGE_ADD_FORM = &H10000
    PRINTER_CHANGE_SET_FORM = &H20000
    PRINTER_CHANGE_DELETE_FORM = &H40000
    PRINTER_CHANGE_FORM = &H70000
    PRINTER_CHANGE_ADD_PORT = &H100000
    PRINTER_CHANGE_CONFIGURE_PORT = &H200000
    PRINTER_CHANGE_DELETE_PORT = &H400000
    PRINTER_CHANGE_PORT = &H700000
    PRINTER_CHANGE_ADD_PRINT_PROCESSOR = &H1000000
    PRINTER_CHANGE_DELETE_PRINT_PROCESSOR = &H4000000
    PRINTER_CHANGE_PRINT_PROCESSOR = &H7000000
    PRINTER_CHANGE_ADD_PRINTER_DRIVER = &H10000000
    PRINTER_CHANGE_SET_PRINTER_DRIVER = &H20000000
    PRINTER_CHANGE_DELETE_PRINTER_DRIVER = &H40000000
    PRINTER_CHANGE_PRINTER_DRIVER = &H70000000
    PRINTER_CHANGE_TIMEOUT = &H80000000
End Enum

Public Enum JobChangeNotificationFields
    JOB_NOTIFY_FIELD_PRINTER_NAME = &H0
    JOB_NOTIFY_FIELD_MACHINE_NAME = &H1
    JOB_NOTIFY_FIELD_PORT_NAME = &H2
    JOB_NOTIFY_FIELD_USER_NAME = &H3
    JOB_NOTIFY_FIELD_NOTIFY_NAME = &H4
    JOB_NOTIFY_FIELD_DATATYPE = &H5
    JOB_NOTIFY_FIELD_PRINT_PROCESSOR = &H6
    JOB_NOTIFY_FIELD_PARAMETERS = &H7
    JOB_NOTIFY_FIELD_DRIVER_NAME = &H8
    JOB_NOTIFY_FIELD_DEVMODE = &H9
    JOB_NOTIFY_FIELD_STATUS = &HA
    JOB_NOTIFY_FIELD_STATUS_STRING = &HB
    JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR = &HC
    JOB_NOTIFY_FIELD_DOCUMENT = &HD
    JOB_NOTIFY_FIELD_PRIORITY = &HE
    JOB_NOTIFY_FIELD_POSITION = &HF
    JOB_NOTIFY_FIELD_SUBMITTED = &H10
    JOB_NOTIFY_FIELD_START_TIME = &H11
    JOB_NOTIFY_FIELD_UNTIL_TIME = &H12
    JOB_NOTIFY_FIELD_TIME = &H13
    JOB_NOTIFY_FIELD_TOTAL_PAGES = &H14
    JOB_NOTIFY_FIELD_PAGES_PRINTED = &H15
    JOB_NOTIFY_FIELD_TOTAL_BYTES = &H16
    JOB_NOTIFY_FIELD_BYTES_PRINTED = &H17
End Enum

Public Type PRINTER_NOTIFY_OPTIONS
    Version As Long
    Flags As Long
    Count As Long
    lpPrintNotifyOptions As Long
End Type

Public Type PRINTER_NOTIFY_OPTIONS_TYPE
    Type As Integer
    Reserved_0 As Integer
    Reserved_1 As Long
    Reserved_2 As Long
    Count As Long
    pFields As Long
End Type

Public Type PRINTER_NOTIFY_INFO_DATA
  Type As Integer
  Field As Integer
  Reserved As Long
  id As Long
  adwData(0 To 1) As Long
End Type

Public Type PRINTER_NOTIFY_INFO
  dwVersion As Long
  dwFlags As Long
  dwCount As Long
End Type

Const INFINITE = &HFFFF ' Infinite timeout
Const JOB_NOTIFY_TYPE = &H1
Const JOB_CONTROL_DELETE = 5

Public Sub Main()
    Dim hPrinter As Long, lngResult As Long, ForeHwnd As Long
    Dim mEventHandle As Long
    Dim PrintOptions As PRINTER_NOTIFY_OPTIONS
    Dim PrinterNotifyOptions(0 To 0) As PRINTER_NOTIFY_OPTIONS_TYPE
   
    lngResult = OpenPrinter(Printer.DeviceName, hPrinter, ByVal vbNullString)
   
    If lngResult <> 0 Then
        With PrintOptions
            .Version = 2 '\\ This must be set to 2
            .Count = 1 '\\ There is job notification
            With PrinterNotifyOptions(0)
                .Type = JOB_NOTIFY_TYPE
                ReDim pFieldsJob(0 To 22) As Integer
                pFieldsJob(0) = JOB_NOTIFY_FIELD_PRINTER_NAME
                pFieldsJob(1) = JOB_NOTIFY_FIELD_MACHINE_NAME
                pFieldsJob(2) = JOB_NOTIFY_FIELD_PORT_NAME
                pFieldsJob(3) = JOB_NOTIFY_FIELD_USER_NAME
                pFieldsJob(4) = JOB_NOTIFY_FIELD_NOTIFY_NAME
                pFieldsJob(5) = JOB_NOTIFY_FIELD_DATATYPE
                pFieldsJob(6) = JOB_NOTIFY_FIELD_PRINT_PROCESSOR
                pFieldsJob(7) = JOB_NOTIFY_FIELD_PARAMETERS
                pFieldsJob(8) = JOB_NOTIFY_FIELD_DRIVER_NAME
                pFieldsJob(9) = JOB_NOTIFY_FIELD_DEVMODE
                pFieldsJob(10) = JOB_NOTIFY_FIELD_STATUS
                pFieldsJob(11) = JOB_NOTIFY_FIELD_STATUS_STRING
                pFieldsJob(12) = JOB_NOTIFY_FIELD_DOCUMENT
                pFieldsJob(13) = JOB_NOTIFY_FIELD_PRIORITY
                pFieldsJob(14) = JOB_NOTIFY_FIELD_POSITION
                pFieldsJob(15) = JOB_NOTIFY_FIELD_SUBMITTED
                pFieldsJob(16) = JOB_NOTIFY_FIELD_START_TIME
                pFieldsJob(17) = JOB_NOTIFY_FIELD_UNTIL_TIME
                pFieldsJob(18) = JOB_NOTIFY_FIELD_TIME
                pFieldsJob(19) = JOB_NOTIFY_FIELD_TOTAL_PAGES
                pFieldsJob(20) = JOB_NOTIFY_FIELD_PAGES_PRINTED
                pFieldsJob(21) = JOB_NOTIFY_FIELD_TOTAL_BYTES
                .Count = (UBound(pFieldsJob) - LBound(pFieldsJob)) + 1 '\\ Add one as the array is zero based
                .pFields = VarPtr(pFieldsJob(0))
            End With
            .lpPrintNotifyOptions = VarPtr(PrinterNotifyOptions(0))
        End With
       
        mEventHandle = FindFirstPrinterChangeNotificationLong(hPrinter, 0, 0, VarPtr(PrintOptions))

watchstart:
        Call WaitForSingleObject(mEventHandle, INFINITE)
       
        Dim lpPrintInfoBuffer As Long
        Dim pdwChange As Long
        Dim aData() As PRINTER_NOTIFY_INFO_DATA
        Dim mData As PRINTER_NOTIFY_INFO
       
        Call FindNextPrinterChangeNotificationByLong(mEventHandle, pdwChange, PrintOptions, lpPrintInfoBuffer)
       
        mData.dwCount = 0
        Call CopyMemoryPRINTER_NOTIFY_INFO(mData, lpPrintInfoBuffer, Len(mData))
        If mData.dwCount > 0 Then
            ReDim aData(1 To mData.dwCount) As PRINTER_NOTIFY_INFO_DATA
            Call CopyMemoryPRINTER_NOTIFY_INFO_DATA(aData(1), lpPrintInfoBuffer + Len(mData), Len(aData(1)) * mData.dwCount)

            Dim lchange As Long
            Dim sValue As String
                   
            sValue = String$(1000, 0)
'            GetComputerName ByVal sValue, 1000
           
'            If InStr(1, StringFromPointer(aData(1).adwData(1), aData(1).adwData(0)), sValue, vbTextCompare) > 0 Then
'                JOB_NOTIFY_FIELD_STATUS
                SetJob hPrinter, aData(1).id, 0, 0, JOB_CONTROL_DELETE
'            End If
            Erase aData
            Call FreePrinterNotifyInfoByLong(lpPrintInfoBuffer)
            ForeHwnd = GetForegroundWindow
        End If
        GoTo watchstart
    End If
End Sub

Public Function StringFromPointer(lpString As Long, lMaxlength As Long) As String
    Dim sRet As String
    Dim lRet As Long
   
    If lpString = 0 Then
        StringFromPointer = ""
        Exit Function
    End If
   
    If Not IsBadStringPtrByLong(lpString, lMaxlength) And lMaxlength < 2048 Then
        sRet = String$(lMaxlength, 0)
        CopyMemory ByVal sRet, ByVal lpString, ByVal Len(sRet)
        If InStr(sRet, Chr$(0)) > 0 Then
            sRet = Left$(sRet, InStr(sRet, Chr$(0)) - 1)
        End If
    End If
    StringFromPointer = sRet
End Function
'--------------------------------------------------
0
 

Author Comment

by:vinaysamant
ID: 10849089
Hi Ark,
No answer since long? Whats the problem? Or r u enjoying week end?

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10851401
Hello
Sorry for delay - I was out of here for a few days.

1.>> Call WaitForSingleObject(mEventHandle, INFINITE)
This waits infinite right! There is function which can wait for many objects at the same time using array of hEventHandle i hope. ITs there on the msdn website. Can we use it so that we can monitor all printers in same application? <<
Yes, you can use smth like this:
For i = 1 to nPrinters
     lngResult = OpenPrinter(Printer(i).DeviceName, hPrinter(i), ByVal vbNullString)
     With PrintOptions
'Fill UDT. Note - try to eleminate pFieldsJob() fields with fields you exactly need only, for example, remove JOB_NOTIFY_FIELD_PRINTER_NAME etc - this will save you a lot of machine time.
     End With

    mEventHandle(i) = FindFirstPrinterChangeNotificationLong(hPrinter(i), 0, 0, VarPtr(PrintOptions))
Next i

watchstart:
        ret = WaitForSingleObject(i, mEventHandle(i), False, INFINITE)
        'Debug.Print "Printer " & ret + 1 & " signaled"
        'Other staff

2. As for job info:
Private Declare Function GetJob Lib "winspool.drv" Alias "GetJobA" (ByVal hPrinter As Long, ByVal JobId As Long, ByVal Level As Long, pJob As Byte, ByVal cdBuf As Long, pcbNeeded As Long) As Long

Private Type JOB_INFO_1
        JobId As Long
        pPrinterName As String
        pMachineName As String
        pUserName As String
        pDocument As String
        pDatatype As String
        pStatus As String
        Status As Long
        Priority As Long
        Position As Long
        TotalPages As Long
        PagesPrinted As Long
        Submitted As SYSTEMTIME
End Type

'Note - you can use JOB_INFO_2 structure which have more info.
'For samples on retrieving job info see http://www.mvps.org/vb/code/PrnInfo.zip

3. For retrieving exe name from handle see my post fro 04/13

4. As for task manager - try to set Cancel = 1 in Form_QueryUnload event

0
 

Author Comment

by:vinaysamant
ID: 10851908
Hi Ark,
thanks for being in touch. See as soon as you use  WaitForSingleObject function with INFINITE option, i will halt there it self & wont move in for loop of printers untill it gets notified. The functions to wait for multiple events is specified on - http://msdn.microsoft.com/library/en-us/dllproc/base/waitformultipleobjects.asp have a look and just let me know, if you can help me out there.

As you specified i should elimintae pFindJob() fields, I just want to get notified as soon as job gets submitted or spooled. Then what 2 options should i specify? Can u please post their names? Rest i will handle.

Getting exe handle is GREAT!

Last thing, Cancel = true works only in case of Alt+F4 or X button on top right corner. If you go through task manager, It closes it any how. No use of Cancel = 1. Basically application remains visible in Task Manager. What am I suppose to do if I want the application to be hidden from Task Manager? Like Nortan Antivirus/yahoo messenger/msn are invisible in Applications of Task Maanger but visible in Processes area. That way i want my application to be running as process. Is it possible? What API should I refer for all this?

Are you from russia? I mailed you last time, but u replied through EE. Dont you like to maintain contacts through mails? Can we be friends? If you dont mind ;)

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10851975
Hello
Sorry, I wrote incorrect call to function:

        ret = WaitForSingleObject(i, mEventHandle(i), False, INFINITE)
should be
        ret = WaitForMultipleObject(i, mEventHandle(i), False, INFINITE)
   
As for infinite loop - there are 2 ways to make your app work normaly, without freezing:
1. Use finite timeout in a loop:
   Do
     DoEvents
     ret = WaitForSingleObject(mEventHandle, 100) 'Wait 100 ms.
     If ret<>WAIT_TIMEOUT Then
        'Process result
     End if
   Loop
2. Use Client/Server relations like Merrion suggested. In this case Server part with Notification code will sleep until notification event occure and client part (your app) will work normally. Then server signaled to client that notify event occured and send info about event - your app here check info and cancel print if nessesary.

Yes, I'm from Russia and I'll be glad to communicate via email. I post code/ideas here because smbd can have same problems as you and can get solution from this thread now and when it'll be PAQed

Regards
Ark
0
 

Author Comment

by:vinaysamant
ID: 10852005
Hi Ark,
Last thing, Cancel = true works only in case of Alt+F4 or X button on top right corner. If you go through task manager, It closes it any how. No use of Cancel = 1. Basically application remains visible in Task Manager. What am I suppose to do if I want the application to be hidden from Task Manager? Like Nortan Antivirus/yahoo messenger/msn are invisible in Applications of Task Maanger but visible in Processes area. That way i want my application to be running as process. Is it possible? What API should I refer for all this?

You have answered in minutes. MKeans u r online at moment. Do you have any yahoo or msn id at which we can chat? my yahoo id is vinusa500, msn id is vinusa500@hotmail.com

Vinay Samant.
0
 
LVL 27

Expert Comment

by:Ark
ID: 10852008
Hi
Try App.TaskVisible = False
Yes, I'm online now, but I haven't yahoo/msn/icq ids.
0
 

Author Comment

by:vinaysamant
ID: 10852051
ARK YOU R GINI!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
App.TaskVisible worked!

But task manager still able to kill it! Can we avoid that? This is last problem remaining. If not possible then pls let me know. Otherwise i will close the problem. I m satisfied with the answers!

My new question posted at - http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_20957522.html

Have a look at please!

With regards & thanks,
Vinay Samant.
0
 

Author Comment

by:vinaysamant
ID: 10852052
ARK YOU R GINI!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
App.TaskVisible worked!

But task manager still able to kill it! Can we avoid that? This is last problem remaining. If not possible then pls let me know. Otherwise i will close the problem. I m satisfied with the answers!

My new question posted at - http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_20957522.html

Have a look at please!

With regards & thanks,
Vinay Samant.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Introduction I needed to skip over some file processing within a For...Next loop in some old production code and wished that VB (classic) had a statement that would drop down to the end of the current iteration, bypassing the statements that were c…
When trying to find the cause of a problem in VBA or VB6 it's often valuable to know what procedures were executed prior to the error. You can use the Call Stack for that but it is often inadequate because it may show procedures you aren't intereste…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now