Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.

Posts on this page:

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/05/19/scardestablishcontext-fails-with-scard_e_no_service-error/
Post name: SCardEstablishContext fails with SCARD_E_NO_SERVICE error
Original author: Alejandro Campos Magencio
Posting date: 2011-05-19T07:52:00+00:00


Hi all,

If you call SCardEstablishContext API in a Windows service running under a specific user account (domain\user) or NETWORK SERVICE, you may get the following error on Vista/Server 2008 or Windows 7/Server 2008 R2:

0x8010001d - SCARD_E_NO_SERVICE - "The Smart card resource manager is not running.".

If the service runs as SYSTEM, the same code works. If the API is called in a standard desktop application, it works too. Additionally, the same service works fine on Windows XP. Why?

SCardEstablishContext API is returning that error because it gets an Access Denied error when trying to open an event called "Global\Microsoft Smart Card Resource Manager Started" with OpenEvent API. The default security for that event on Vista and Windows 7 specifies that only SYSTEM, LOCAL SERVICE and INTERACTIVE users have access to it. NETWORK SERVICE or non-interactive users won’t be able to access the event.

Enabling "Allow service to interact with desktop" won't help.

When looking for workaroundsother than the obvious (use any of the accounts with default access to the event), I found a solution that a colleague of mine applied in a similar case in the past. They used the following code to grant users access to the event:

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD result = AddAceToObjectsSecurityDescriptor (
        _T("Global\\Microsoft Smart Card Resource Manager Started"), 
        SE_KERNEL_OBJECT, 
        _T("Everyone"), 
        TRUSTEE_IS_NAME, 
        GENERIC_ALL, 
        GRANT_ACCESS, 
        NO_INHERITANCE);
    _tprintf(_T("Result = %d\n"), result);
}

AddAceToObjectsSecurityDescriptor method can be found here: Modifying the ACLs of an Object in C++. Note I haven’t tried this code myself.

Additionally, this issue didn’t happen on Windows XP because on older OS versions LOCAL accounts had permissions on the event instead of INTERACTIVE accounts like on Vista/Server 2008 or Windows 7/Server 2008 R2.

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)

 

UPDATE!!! Some customers are reporting that the code above returns Access Denied errors when running it with elevated admin accounts, with admin accounts and UAC disabled, and even with System account. But it looks like running it with LOCAL SERVICE works.Actually, I verified with Process Explorer that only LOCAL SERVICE account can modify the event (note that with that tool we can modify the security descriptor of the event too).

Now, we must take into account that the security of the event is set when the smart card service creates the event (see Synchronization Object Security and Access Rights for details).This means that when we restart the machine, we will lose the changes we made to the security descriptor.

So if changing the permissions every time you restart the machine or using one of the default accounts won't work for you, I'm afraid there is nothing we can do at the moment.

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/04/26/how-to-automate-a-command-line-utility-like-diskpart-vbscript/
Post name: How to automate a command line utility like DiskPart (vbscript)
Original author: Alejandro Campos Magencio
Posting date: 2011-04-26T08:08:15+00:00


Hi all,

The other day I created a VBScript to automate DiskPart.exe tool and be able to perform tasks on a disk after selecting it by LUN ID. Note that Diskpart only allow us to select disks by their index number or their location path.

Bellow you can see a sample Diskpart.exe script, and how I managed to do the samestuffwith VBScript after finding the right disk by its LUN ID. In general, this sample will show you how automate any other command line tool by sending commands to it and reading and parsing its output:

' ===================================================================
' Sample diskpart script:
'
'   rescan
'   sel disk 37
'   sel vol 0
'   assign letter f
'   exit
'
' ===================================================================
' PARAMETERS
' ===================================================================

' Get LUN ID and letter to assign from user. 
If Wscript.Arguments.Count <> 2 Then 
    ' If we didn't pass them as params to the script, ask the user 
    ' for them
    iLunId = CInt(InputBox("Enter LUN ID"))
    cLetter = InputBox("Enter letter")
Else
    iLunId = CInt(Wscript.Arguments(0))
    cLetter = Wscript.Arguments(1)
End If

' ===================================================================
' MAIN
' ===================================================================
' Run diskpart
set objShell = WScript.CreateObject("WScript.Shell")
set objExec = objShell.Exec("diskpart.exe")
' Let's find the disk which LUN ID we need
' Rescan the disks
strOutput = ExecuteDiskPartCommand("rescan")
WScript.echo strOutput
' Select the first disk
strOutput = ExecuteDiskPartCommand("sel disk 0")
WScript.echo strOutput
' Iterate through the disks until we find the one we need
bFoundDisk = false
Do While InStr(strOutput, "There is no disk selected") = 0
    ' See current disk details
    strOutput = ExecuteDiskPartCommand("detail disk")
    WScript.echo strOutput
    ' Is this the disk which LUN ID is the one we are looking for?
    If (GetLunId(strOutput) = iLunId) Then
        ' Yes. Launch the rest of the commands on the original diskpart script
        strOutput = ExecuteDiskPartCommand("sel vol 0")
        WScript.echo strOutput
        strOutput = ExecuteDiskPartCommand("assign letter " & cLetter)
        WScript.echo strOutput
        ' We are done. We can leave now
        bFoundDisk = true
        Exit Do
    End If
    ' We didn't find the disk. Move to the next one
    strOutput = ExecuteDiskPartCommand("sel disk next")
    WScript.echo strOutput    
Loop
' We can close diskpart already
ExitDiskPart
WScript.echo "***********************************************************************"
WScript.echo "Script has finished. DiskPart has been closed"
If bFoundDisk Then
     WScript.echo "RESULT: We successfully run desired commands on disk with LUN ID = " & iLunId
Else
    WScript.echo "RESULT: We COULD NOT find the disk with LUN ID = " & iLunId
End If
WScript.echo "***********************************************************************"

' ===================================================================
' HELPER FUNCTIONS
' ===================================================================
Function ExecuteDiskPartCommand (strCommand)
    ' Run the command we want
    objExec.StdIn.Write strCommand & VbCrLf
    ' If we read the output now, we will get the one from previous command (?). As we will always
    ' run a dummy command after every valid command, we can safely ignore this
    Do While True
        IgnoreThis = objExec.StdOut.ReadLine & vbcrlf              
        ' Command finishes when diskpart prompt is shown again
        If InStr(IgnoreThis, "DISKPART>") <> 0 Then Exit Do
    Loop
    ' Run a dummy command, so the next time we call this function and try to read output,
    ' we can safely ignore the result
    objExec.StdIn.Write VbCrLf
    ' Read command's output
    ExecuteDiskPartCommand = ""    
    Do While True
        ExecuteDiskPartCommand = ExecuteDiskPartCommand & objExec.StdOut.ReadLine & vbcrlf              
        ' Command finishes when diskpart prompt is shown again
        If InStr(ExecuteDiskPartCommand, "DISKPART>") <> 0 Then Exit Do
    Loop
End Function

Function GetLunId(strDetails)
    ' Parse the details of the disk
    arrayLines = Split(strDetails, vbcrlf)
    for i = LBound(arrayLines) to UBound(arrayLines)
        If (InStr(arrayLines(i), "LUN ID : ") <> 0) Then
            ' We found the LUN ID. Return it
            GetLunId = CInt(Mid(arrayLines(i), Len("LUN ID : ")))
            Exit For
        End If
    next
End Function

Sub ExitDiskPart
    ' Run exit command to exit the tool
    objExec.StdIn.Write "exit" & VbCrLf
End Sub

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/03/24/signtool-fails-with-error-0x80092006-on-windows-server-2008/
Post name: SignTool fails with error 0x80092006 on Windows Server 2008
Original author: Alejandro Campos Magencio
Posting date: 2011-03-24T04:00:00+00:00


Hi all,

The other day a customer of mine was trying to sign a binary with SignTool.exe using the following command line on Windows Server 2008 SP2:

Signtool.exe sign /a /f "c:\mycert.pfx" /p "password" /t "http://timestamp.verisign.com/scripts/timstamp.dll" "c:\temp\myFile.exe"

And SignTool failed with the following error:

SignTool Error: ISignedCode::Sign returned error: 0x80092006 No provider was specified for the store or object.

He got the same error on Windows Vista. Now, the same command worked just fine on Windows 7 and Windows Server 2008 R2. He had the latest Windows SDK installed.

I debugged the issue and saw that on Windows Vista/ Server 2008 RTM/SP1/SP2, SignTool failed because the SignerSignEx API that it calls behind the scenes failed with the same error80092006:

Now, the API returned that error because it got this other error internally:

0x80090014Invalid provider type specified

And it got this internal error because when trying to acquire the crypto provider with CryptAcquireCertificatePrivateKey API, it didn't useCRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, and the provider associated to customer'scertificate was a CNG provider ("Microsoft Software Key Storage Provider" in this case).

I also verified thatSignerSignEx API ends up calling CryptAcquireCertificatePrivateKey with CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAGonWindows 7/Server 2008 R2 RTM/SP1, so it should work with CNG certificates in those environments.

So summing up, we are facing a limitation on Vista/Server 2008 that won’t allow us to use CNG certificates with SignerSignEx API or the APIs, COM objects and tools (e.g. SignTool.exe) that end up calling SignerSignEx behind the scenes.

Here I posted the ways to sign binaries that we support, and all of them end up in a call to SignerSignEx, so they are all affected by this limitation: How to sign EXE files with an Authenticode certificate (part 2)

 

 

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/03/24/how-to-update-run-only-specified-windows-applications-gpo-programmatically-vbscript/
Post name: How to update "Run only specified windows applications" GPO programmatically (VBScript)
Original author: Alejandro Campos Magencio
Posting date: 2011-03-24T03:38:00+00:00


Hi all,

A customer of mine had to add a list of thousands of applications to the "Run only specified windows applications" GPO on his Windows Server 2008. And obviously he wanted to automate this task to avoid adding them manually one at a time.

So I went the easy way and created the following VBscript that simulates a user entering the application names on the "List of allowed applications" in the "Show Contents" window of the GPO.

This script takes a file with a list of application names, and sends those names to the window automatically. Note that we must select the place in the"List of allowed applications"where we want to enter the data before running the script.

' Open the text file, located in the same path as the script
Set objFSO = CreateObject("Scripting.FileSystemObject")
strPath = Mid(Wscript.ScriptFullName, 1, InStrRev(Wscript.ScriptFullName, wscript.ScriptName) -1)
Set objFile = objFSO.OpenTextFile(strPath & "applist.txt")
' Activate the "Show Contents" window with the "List of allowed applications".
' Note the window must be opened already and we should have selected where in 
' the list we want to enter the data before running the script
set WshShell = WScript.CreateObject("WScript.Shell")
WScript.Sleep 1000
WshShell.AppActivate "Show Contents"

' Read the file line by line
Do While objFile.AtEndOfStream <> True
    ' Each line contains one EXE name
    exeName = objFile.ReadLine
    ' Escape forbidden chars { } [ ] ( ) + ^ % ~
    exeName = Replace(exeName, "[", "{[}")
    exeName = Replace(exeName, "]", "{]}")
    exeName = Replace(exeName, "(", "{(}")
    exeName = Replace(exeName, ")", "{)}")
    exeName = Replace(exeName, "+", "{+}")
    exeName = Replace(exeName, "^", "{^}")
    exeName = Replace(exeName, "%", "{%}")
    exeName = Replace(exeName, "~", "{~}") 
    ' Send the EXE name to the window
    WScript.Sleep 100
    WshShell.SendKeys exeName
    ' Move to the next one
    WshShell.SendKeys "{TAB}"    
Loop
objFile.Close

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/03/23/filesystemwatcher-class-does-not-fire-change-events-when-notifyfilters-size-is-used/
Post name: FileSystemWatcher class does not fire Change events when NotifyFilters.Size is used
Original author: Alejandro Campos Magencio
Posting date: 2011-03-23T09:55:00+00:00


Hi all,

A customer of mine was trying to monitor a .log file in a specific folder with .NET's FileSystemWatcher class and a code like the following:

static void Main(string[] args)
{
    FileSystemWatcher fsw = new FileSystemWatcher(@"C:\mypath", "myfile.log");
    fsw.NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite;
    fsw.Changed += new FileSystemEventHandler(MyChangedMethod);
    fsw.Error += new ErrorEventHandler(MyErrorMethod);
    fsw.EnableRaisingEvents = true;
    while (true);
}

static void MyErrorMethod(object sender, ErrorEventArgs e)
{
    System.Console.WriteLine("Error= " + e.ToString());
}

static void MyChangedMethod(object sender, FileSystemEventArgs e)
{
   FileInfo info = new FileInfo(e.FullPath);
   System.Console.WriteLine("New size= " + info.Length + ". File= " + e.FullPath);    
}

Another process was updating the .log file when it produced log messages.

A Changed event should be raised whenever the size of the specified file changes. This works on Windows XP/Server 2003. Now, on Windows Vista and later OS versions, this is not true.

The issue is caused by the second process updating the .log file with the Flush method, and a known limitation in this .NET method:

The Flush method of the FileStream class does not cause Windows to flush the file's metadata. This has the side-effect of not notifying any applications using FileSystemWatcher of file changes (writes) or LastWrite changes. The metadata is not flushed until the FileStream.Close method is called; at which time notifications are picked up by the native API ReadDirectoryChangesW used by FileSystemWatcher behind the scenes.

Now, why does it work on XP/Server 2003? Optimization took place on Vista/Server 2008 and later, and file notifications don’t come as regularly as before, only on close now. So if we don’t close the file, which Flush doesn’t do, we won't get the expected notifications.

The obvious workaround would be to close the file when you want FileSystemWatcher to get the event, and reopen it to continue writing.

Regards,

Alex (Alejandro Campos Magencio)