Play wave file from a Windows Service (C#) - c#

I need to play a wav file from a C# application running as a Windows Service. I have tried both System.Media.SoundPlayer and a P/Invoke call to WinMM.dll (which is probably what SoundPlayer is doing).
[DllImport("WinMM.dll")]
private static extern bool PlaySound(string fname, int Mod, int flag);
If I run my code as a console application, the sounds play. When I run it from a service, no luck, and I guess I'm not surprised.
So is there a way to play a sound from a windows service? Would something like DirectSound help? Or am I going to be stuck writing a console application and having the windows service app communicate with it as an intermediary?
Thanks in advance

Playing a wav file from a service is definitely possible, at least on Windows 7 (and most likely Vista), by using the Windows Core Audio APIs. I recently verified this by making a small test service using NAudio. I just downloaded the NAudio sources and copied the "Wsapi" parts from their NAudioDemo project. This was on Windows 7 Enterprise 64bit, but I don't think that matters. The service was using the LocalSystem account.
For the record, playing sounds from a service is a perfectly legitimate thing to do in an embedded setting.

You can do this via the PlaySound API via winmm.dll, in Windows Vista or above. Microsoft added a seperate session for 'System Sounds' that can be used even from services, by merely adding a flag.
I've formatted this properly to avoid issues with the c# 2017 IDE throwing a wobbly over the DllImport not being in a class named 'NativeMethods'.
using System.Runtime.InteropServices;
namespace Audio
{
internal static class NativeMethods
{
[DllImport("winmm.dll", EntryPoint = "PlaySound", SetLastError = true, CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true)]
public static extern bool PlaySound(
string szSound,
System.IntPtr hMod,
PlaySoundFlags flags);
[System.Flags]
public enum PlaySoundFlags : int
{
SND_SYNC = 0x0000,/* play synchronously (default) */
SND_ASYNC = 0x0001, /* play asynchronously */
SND_NODEFAULT = 0x0002, /* silence (!default) if sound not found */
SND_MEMORY = 0x0004, /* pszSound points to a memory file */
SND_LOOP = 0x0008, /* loop the sound until next sndPlaySound */
SND_NOSTOP = 0x0010, /* don't stop any currently playing sound */
SND_NOWAIT = 0x00002000, /* don't wait if the driver is busy */
SND_ALIAS = 0x00010000,/* name is a registry alias */
SND_ALIAS_ID = 0x00110000, /* alias is a pre d ID */
SND_FILENAME = 0x00020000, /* name is file name */
SND_RESOURCE = 0x00040004, /* name is resource name or atom */
SND_PURGE = 0x0040, /* purge non-static events for task */
SND_APPLICATION = 0x0080, /* look for application specific association */
SND_SENTRY = 0x00080000, /* Generate a SoundSentry event with this sound */
SND_RING = 0x00100000, /* Treat this as a "ring" from a communications app - don't duck me */
SND_SYSTEM = 0x00200000 /* Treat this as a system sound */
}
}
public static class Play
{
public static void PlaySound(string path, string file = "")
{
NativeMethods.PlaySound(path + file, new System.IntPtr(), NativeMethods.PlaySoundFlags.SND_ASYNC | NativeMethods.PlaySoundFlags.SND_SYSTEM);
}
}
}

Applied the NAudio to simply allow to play audio file.
http://bresleveloper.blogspot.co.il/2012/06/c-service-play-sound-with-naudio.html

You've chosen the wrong application type. A windows service is for longer running applications that execute non-interactively, whether or not someone has logged into the computer. For example SQL Server, IIS etc.
You are also prevented in Windows Vista and later, from displaying user interface windows from a windows service. For Windows XP,2000 Server and you can display a MessageBox, however this is not recommended for most services.
So in general, services are not permitted to be "interactive", this includes playing sounds, multimedia etc.
You either need to change the application type to a normal Console/Windows Forms application, or live without playing sounds from your service.
For more information see this page on interactive services and related pages at MSDN.

Related

How do I retrieve a Windows Store app's icon from a C# desktop app?

In C#, getting an .exe file's icon is easy. You just do Icon.ExtractAssociatedIcon(path) and voilĂ .
But what if the app I want to get the icon of is not a standard .exe, but one of these newfangled Windows Store apps?
Is there a way to get the icon of a Windows Store app as a Bitmap object in my C# app?
In case of windows store UWP application you will have application icon path in form of so called "Indirect string".
For example for Groove music:
#{Microsoft.ZuneMusic_10.18011.12711.0_x64__8wekyb3d8bbwe?ms-resource://Microsoft.ZuneMusic/Files/Assets/AppList.png}
To extract normal path from indirect string SHLoadIndirectString windows API function can be used.
using System;
using System.Runtime.InteropServices;
using System.Text;
class IndirectString
{
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
public string ExtractNormalPath(string indirectString)
{
StringBuilder outBuff = new StringBuilder(1024);
int result = SHLoadIndirectString(indirectString, outBuff, outBuff.Capacity, IntPtr.Zero);
return outBuff.ToString();
}
}
This question has been answered on superuser for general approach:
https://superuser.com/questions/478975/how-to-create-a-desktop-shortcut-to-a-windows-8-modern-ui-app
If you want to do it in C# - it partly depends on where you start. Do you want an icon for a specific app or icons for all apps? Do you know the path to the app, app name or app ID?
You can get a list of apps using a PowerShell Get-AppxPackage command. I haven't gone too far in getting it working, but to do that from C# you would install the System.Management.Automation NuGet package, then run something like this:
using (var runspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault()))
{
runspace.Open();
PowerShell powerShellCommand = PowerShell.Create();
powerShellCommand.Runspace = runspace;
powerShellCommand.AddScript("Get-AppxPackage |?{!$_.IsFramework}");
foreach (var result in powerShellCommand.Invoke())
{
try
{
if (result.Properties.Match("Name").Count > 0 &&
result.Properties.Match("InstallLocation").Count > 0)
{
var name = result.Properties["Name"].Value;
var installLocation = result.Properties["InstallLocation"].Value;
Console.WriteLine(installLocation.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
runspace.Close();
}
The problem with the above is that it returns a lot of component packages that are not actual apps. I could not find a simple way to find app display names either. You can crack open the AppxManifest.xml found in the install location, but for any localized app it will have something like "ms-resource:ApplicationTitleWithBranding" for its display name which seems complicated to extract from outside of the app.
To just get a list of apps from the registry you can use the Microsoft.Win32.Registry class to read the registry from "HKEY_CLASSES_ROOT\Extensions\ContractId\Windows.Protocol\PackageId", then use the technique described on superuser to get the tile images.

User32 API custom PostMessage for automatisation

I want to automate a program called Spotify from C#, the best way (I think) to do this is by triggering fake keypresses. I want to program to pause playback, and I don't know enough about this stuff to find another way than keypresses. So I use Visual Studio's Spy++ to see what message Spotify gets when pressing the play button on my keyboard, I copy the data from that message into my Console Application and run it, when I run I can see the PostMessage in Spy++'s Message Logging, so this is working but it doesn't pause/play my music. I guess this is because I also have to send another PostMessage with another destination, but how do I know what else to send?
Post Message call:
MessageHelper.PostMessage((int)hwndSpotify, 0x100, 0x000000B3, 0x01000001);
I hope someone is familiar with this and can help me out.
To automate Spotify, first you have to get the handle of the window with the following class name: SpotifyMainWindow (using FindWindow()).
Then, you can use the SendMessage() method to send a WM_APPCOMMAND message to the Spotify's window.
Following a simple code to do that:
internal class Win32
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
internal class Constants
{
internal const uint WM_APPCOMMAND = 0x0319;
}
}
public enum SpotifyAction : long
{
PlayPause = 917504,
Mute = 524288,
VolumeDown = 589824,
VolumeUp = 655360,
Stop = 851968,
PreviousTrack = 786432,
NextTrack = 720896
}
For instance, to play or pause the current track:
Win32.SendMessage(hwndSpotify, Win32.Constants.WM_APPCOMMAND, IntPtr.Zero, new IntPtr((long)SpotifyAction.PlayPause));
Pressing the "play buttion" results in a virtual key code - for an official list see http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx .
There you find for example VK_VOLUME_UP VK_MEDIA_PLAY_PAUSE VK_ZOOM .
Even some Remotes translate to these codes to be as compatible as possible with existing software.
These were introduced back in the day when Windows ME (!) came out and are still in use - at least when I checked the registry of my Windows 2008 R2 !
Basically Windows translates certain VK* into WM_APPCOMMAND messages with certain codes which the applications listen to...
If the key has something to do with launching an app to do (i.e. Mail, Browser etc.) then the magic happens via Windows Explorer which reads the mapping (either by association or direct exec) from the registry at Software\ Microsoft\ Windows\ CurrentVersion\ Explorer\ AppKey - either HKLM or HKCU.
Some links with old but as it seems still valid information:
http://msdn.microsoft.com/en-us/windows/hardware/gg463446.aspx
http://msdn.microsoft.com/en-us/windows/hardware/gg462991
http://msdn.microsoft.com/en-us/windows/hardware/gg463372

Play wav file async multiple times with .net

I have an app that I need to play a wav file when a key is pressed, I use the SoundPlayer class but if another sound is being played when a new key is pressed, it stops the sound the play it again making it look ugly....
is there any way to play the same sound again even if there is another of being played?
thanks!
Use PlaySound from the windows API in combination with the SND_ASYNC and SND_NOSTOP flags.
http://www.pinvoke.net/default.aspx/winmm.playsound
Usage
//And the actual usage
PlaySound (fileName, UIntPtr.Zero, (uint)(SoundFlags.SND_FILENAME | SoundFlags.SND_ASYNC | SoundFlags.SND_NOSTOP));
Declarations
[Flags]
public enum SoundFlags
{
/// <summary>play synchronously (default)</summary>
SND_SYNC = 0x0000,
/// <summary>play asynchronously</summary>
SND_ASYNC = 0x0001,
/// <summary>silence (!default) if sound not found</summary>
SND_NODEFAULT = 0x0002,
/// <summary>pszSound points to a memory file</summary>
SND_MEMORY = 0x0004,
/// <summary>loop the sound until next sndPlaySound</summary>
SND_LOOP = 0x0008,
/// <summary>don't stop any currently playing sound</summary>
SND_NOSTOP = 0x0010,
/// <summary>Stop Playing Wave</summary>
SND_PURGE = 0x40,
/// <summary>don't wait if the driver is busy</summary>
SND_NOWAIT = 0x00002000,
/// <summary>name is a registry alias</summary>
SND_ALIAS = 0x00010000,
/// <summary>alias is a predefined id</summary>
SND_ALIAS_ID = 0x00110000,
/// <summary>name is file name</summary>
SND_FILENAME = 0x00020000,
/// <summary>name is resource name or atom</summary>
SND_RESOURCE = 0x00040004
}
[DllImport("winmm.dll", SetLastError=true)]
static extern bool PlaySound(string pszSound, UIntPtr hmod, uint fdwSound);
Update
Apologies, you are correct. The API cannot be used to play sounds simultaneously. You should be using the waveOut api. Look at this article: http://www.codeproject.com/KB/audio-video/cswavrec.aspx
NAudio might offer what you need. I haven't used it myself, but it's relatively popular.
There's a free (for non-commercial applications) sound library for .NET called Irrklang that supports playing multiple sounds (at least wav and mp3 from what I can gather) at the same time, their tutorial specifically covers this case as basic usage of the library.
If you're asking, "how can I play multiple sounds at once" then SoundPlayer will never be the answer.
PlaySound, I believe, is also similarly limited.
You might look at this question and this question for more options on sound APIs. I took a quick look at SDL and SDL Mixer and thought that SDL was too primitive (you have to mix the sounds yourself) and SDL Mixer was too heavyweight (it's all that and a bag of chips - unlimited channels of mixing and music (mp3, ogg, midi, etc)). I couldn't see an online reference for BASS's C# bindings, but it's free for non-commercial use.

What is the most secure way to retrieve the system Drive

I know that the following should work:
Environment.GetEnvironmentVariable("windir", EnvironmentVariableTarget.Machine)
My problem with this call is that if for some reason someone decided to remove the "windir" Env Var , this won't work.
Is there an even more secure way to get the System drive?
string windir = Environment.SystemDirectory; // C:\windows\system32
string windrive = Path.GetPathRoot(Environment.SystemDirectory); // C:\
Note: This property internally uses the GetSystemDirectory() Win32 API. It doesn't rely on environment variables.
This one returns the path to the system directory (system32).
Environment.GetFolderPath(Environment.SpecialFolder.System)
You may be able to use that, then you don't need to rely on environment variables.
One thing i actually maybe misunderstand is that you want the System Drive, but by using "windir" you'll get the windows folder. So if you need a secure way to get the windows folder, you should use the good old API function GetWindowsDirectory.
Here is the function prepared for C# usage. ;-)
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetWindowsDirectory(StringBuilder lpBuffer, uint uSize);
private string WindowsDirectory()
{
uint size = 0;
size = GetWindowsDirectory(null, size);
StringBuilder sb = new StringBuilder((int)size);
GetWindowsDirectory(sb, size);
return sb.ToString();
}
So if you really need the drive on which windows is running, you could afterwards call
System.IO.Path.GetPathRoot(WindowsDirectory());
You can use the GetWindowsDirectory API to retrieve the windows directory.
Never read environment variables (any script or user can change them !)
The official method (MS internal, used by Explorer) is a Win32 api FAQ for decades (see Google groups, Win32, System api)
Theres an environment variable called SystemDrive
C:\>SET SystemDrive
SystemDrive=C:

16-Bit Apps running under NTVDM

I am trapping for the execution of some old 16-bit applications that our internal folks should no longer be using. They are 1985 DOS apps, so trapping for them was easy... capture any process that launches under NTVDM.exe
Now, the problem is finding out which program NTVDM is actually running under the hood. Apparently there are a coupleof the 1985 programs that they SHOULD be allowed to run, so I need to see the actual EXE name that is hiding under NTVDM.
WqlEventQuery query =
new WqlEventQuery("__InstanceCreationEvent",
new TimeSpan(0, 0, 1),
"TargetInstance isa \"Win32_Process\"");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Start();
...
static void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
ProcessInfo PI = new ProcessInfo();
PI.ProcessID = int.Parse(instance["ProcessID"].ToString());
PI.ProcessName = instance["Name"].ToString();
PI.ProcessPath = instance["ExecutablePath"].ToString();
// Here's the part I need...
PI.ActualEXE = ???;
// ... do the magic on the PI class ...
instance.Dispose();
}
When I capture the instance information, I can get the command line, but the arguments are "-f -i10" ... There is no EXE name on the command line. Is there any other method/property I should be looking at to determine the EXE name of the 16-bit application that's actually running?
UPDATE: Let me refine the question: If I can find the NTVDM process, how can I -- programatically -- know the actual path to the EXE that is being executed underneath?
Thanks.
The trick is not to use VDMEnumProcessWOW (which gives the VDMs), but to use VDMEnumTasksWOW. The enumerator function that you pass to this function will be called for each 16 bit task in the specified VDM.
I haven't checked it myself, but according to the documentation, this library of CodeProject does exactly that, if you pass in the PROC16 enum value. It's C++, if you need help compiling that code and calling it from C#, let me know and I'll give you an example.
A program that uses this technique is Process Master, it comes with full source. I suggest you run it to find out whether it gives the info you need, and if so, you can apply this method to your own application (it doesn't run on Windows Vista or 7, it uses old VB5 code, apparently it's not compatible. It should run on XP).
If things with these functions do not go as planned, you may be on Vista and may need the hotfix described in this StackOverflow question, which points to downloading a hotfix, which is in turn described here:
"An application that uses the
VDMEnumProcessWOW function to
enumerate virtual DOS machines returns
no output or incorrect output on a
computer that is running a 32-bit
version of Windows Vista"
Update: while this seems promising, I applied the patch, ran several versions of the code, including Microsoft's, and while they all work on XP, they fail silently (no error, or wrong return value) on Vista.
The "kinda" working code
Update: I experimented with (amongst others) with the following code, which compiles fine in C# (and can be written simpler, but I didn't want to run a marshal-mistake risk). When you add these functions, you can call Enum16BitProcesses, which will write the filenames of the EXE files of the 16 bit processes to the Console.
I can't run it on Vista 32 bit. But perhaps others can try and compile it, or find the error in the code. It would be nice to know whether it works on other systems:
public class YourEnumerateClass
{
public static void Enum16BitProcesses()
{
// create a delegate for the callback function
ProcessTasksExDelegate procTasksDlgt =
new ProcessTasksExDelegate(YourEnumerateClass.ProcessTasksEx);
// this part is the easy way of getting NTVDM procs
foreach (var ntvdm in Process.GetProcessesByName("ntvdm"))
{
Console.WriteLine("ntvdm id = {0}", ntvdm.Id);
int apiRet = VDMEnumTaskWOWEx(ntvdm.Id, procTasksDlgt, IntPtr.Zero);
Console.WriteLine("EnumTaskWOW returns {0}", apiRet);
}
}
// declaration of API function callback
public delegate bool ProcessTasksExDelegate(
int ThreadId,
IntPtr hMod16,
IntPtr hTask16,
IntPtr ptrModName,
IntPtr ptrFileName,
IntPtr UserDefined
);
// the actual function that fails on Vista so far
[DllImport("VdmDbg.dll", SetLastError = false, CharSet = CharSet.Auto)]
public static extern int VDMEnumTaskWOWEx(
int processId,
ProcessTasksExDelegate TaskEnumProc,
IntPtr lparam);
// the actual callback function, on Vista never gets called
public static bool ProcessTasksEx(
int ThreadId,
IntPtr hMod16,
IntPtr hTask16,
IntPtr ptrModName,
IntPtr ptrFileName,
IntPtr UserDefined
)
{
// using PtrToStringAnsi, based on Matt's comment, if it fails, try PtrToStringAuto
string filename = Marshal.PtrToStringAnsi(ptrFileName);
Console.WriteLine("Filename of WOW16 process: {0}", filename);
return false; // false continues enumeration
}
}
Update: Intriguing read by the renown Matt Pietrek. Mind the sentence, somewhere near the end:
"For starters, MS-DOS-based programs
seem to always run in separate NTVDM
sessions. I was never able to get an
MS-DOS-based program to run in the
same session as a 16-bit Windows-based
program. Nor was I able to get two
independently started MS-DOS-based
programs to run in the same NTVDM
session. In fact, NTVDM sessions
running MS-DOS programs don't show up
in VDMEnumProcessWOW enumerations."
Seems that, to find out what processes are loaded, you'll need to write a hook into NTVDM or write a listener that monitors access to the file. When the application that tries to read a certain DOS file is NTVDM.exe, it's bingo. You may want to write a DLL that's only attached to NTVDM.exe, but now we're getting a bit ahead of ourselves. Long story short: this little ride into NTVDM has shown "possibilities" that appeared real hoaxes in the end.
There's one other way, but time is too short to create an example. You can poke around in the DOS memory segments and the EXE is usually loaded at the same segment. But I'm unsure if that eventually will lead to the same result and whether it's worth the effort.
This works for me:
Follow the instructions at Description of the Software Restriction Policies in Windows XP to open the local or domain policy editor.
Under Software Restriction Policies -> Additional Rules, right click and select New Hash Rule.
Browse to (for example) edit.com. Make sure Security Level is set to Disallowed. Click OK.
Now,
C:\>edit
The system cannot execute the specified program.
(I get the same results from command.com and cmd.exe -- under Win XP)
From this link about VDMDBG functions, you may be able to P/Invoke "VDMEnumProcessWOW()", then enumerate modules within the process using PSAPI.
Note Regarding 16-bit DOS Applications:
None of the VDMDBG functions work with
16-bit DOS applications. To enumerate
DOS VDMs, you need to use another
method. First, you could use
VDMEnumProcessWOW() to make a list of
all Win16 VDMs, and then enumerate all
instances of NTVDM.exe using some
other scheme (such as PSAPI). Any
NTVDM.exe from the full enumeration
that was not in the Win16 list is a
DOS VDM. You can create and terminate
16-bit DOS applications with
CreateProcess() and
TerminateProcess().
Hope that helps...

Categories