I'm trying to create a custom authentication plugin for WMS 2009 in C#.
I managed to implement something that for some reason blocks all requests...
[ComVisible(true)]
[Guid("C0A0B38C-C4FE-43B5-BE9E-C100A83BBCEE")]
public class AuthenticationPlugin : IWMSBasicPlugin, IWMSAuthenticationPlugin, IWMSAuthenticationContext
private const string SubKey = "SOFTWARE\\Microsoft\\Windows Media\\Server\\RegisteredPlugins\\Authentication\\{C0A0B38C-C4FE-43B5-BE9E-C100A83BBCEE}";
[ComRegisterFunction]
public static void RegisterFunction(Type t)
{
try
{
RegistryKey regHKLM = Registry.LocalMachine;
regHKLM = regHKLM.CreateSubKey(SubKey);
regHKLM.SetValue(null, "UC WMS Authentication plugin");
RegistryKey regHKCR = Registry.ClassesRoot;
regHKCR = regHKCR.CreateSubKey("CLSID\\{C0A0B38C-C4FE-43B5-BE9E-C100A83BBCEE}\\Properties");
regHKCR.SetValue("Name", CustomC WMS Authentication plugin");
regHKCR.SetValue("Author", "Me");
regHKCR.SetValue("CopyRight", "Copyright 2009. All rights reserved");
regHKCR.SetValue("Description", "Enables custom WMS authentication");
}
catch (Exception error)
{
Console.WriteLine(error.Message, "Inside RegisterFunction(). Cannot Register.");
}
}
[ComUnregisterFunction]
public static void UnRegisterFunction(Type t)
{
try
{
RegistryKey regHKLM = Registry.LocalMachine;
regHKLM.DeleteSubKey(SubKey);
RegistryKey regHKCR = Registry.ClassesRoot;
regHKCR.DeleteSubKeyTree("CLSID\\{C0A0B38C-C4FE-43B5-BE9E-C100A83BBCEE}");
regHKCR.DeleteSubKeyTree("CSEventTest.CSEventPlugin");
}
catch (Exception error)
{
Console.WriteLine(error.Message, "Cannot delete a subkey.");
}
}
#region IWMSBasicPlugin Members
public void InitializePlugin(IWMSContext serverContext, WMSNamedValues namedValues, IWMSClassObject classFactory)
{
}
public void ShutdownPlugin()
{
}
public void EnablePlugin(ref int flags, ref int heartbeatPeriod)
{
}
public void DisablePlugin()
{
}
public object GetCustomAdminInterface()
{
return null;
}
public void OnHeartbeat()
{
}
#endregion
#region IWMSAuthenticationPlugin Members
public IWMSAuthenticationContext CreateAuthenticationContext()
{
return (IWMSAuthenticationContext)this;
}
public int GetFlags()
{
return Convert.ToInt32(WMS_AUTHENTICATION_FLAGS.WMS_AUTHENTICATION_ANONYMOUS, CultureInfo.InvariantCulture);
}
public string GetPackageName()
{
return "Custom WMS Authentication";
}
public string GetProtocolName()
{
return "Basic";
}
#endregion
#region IWMSAuthenticationContext Members
public void Authenticate(object responseBlob, IWMSContext userContext, IWMSContext presentationContext, IWMSCommandContext commandContext, IWMSAuthenticationCallback callBack, object context)
{
callBack.OnAuthenticateComplete(WMS_AUTHENTICATION_RESULT.WMS_AUTHENTICATION_SUCCESS, null, context);
}
public IWMSAuthenticationPlugin GetAuthenticationPlugin()
{
return (IWMSAuthenticationPlugin)this;
}
public string GetImpersonationAccountName()
{
return String.Empty;
}
public int GetImpersonationToken()
{
return 0;
}
public string GetLogicalUserID()
{
return this.GetImpersonationAccountName();
}
#endregion
}
Can anyone spot why this is happening?
Also, is there any way I could have a look at the code for the standard Anonymous Authentication plugin already installed on the server? Is it in an assembly somewhere?
Thanks.
I ran into the same issue. It isn't enough to return success status from the Authenticate method.
Your implemented method must retrieve a handle to a valid Windows Login. Search the net for C# examples of how to call this method: http://msdn.microsoft.com/en-us/library/aa378184%28VS.85%29.aspx
bool result = LogonAPI.LogonUser("username", "domain", "password", LogonAPI.LOGON32_LOGON_NETWORK, LogonAPI.LOGON32_PROVIDER_DEFAULT, ref _userToken);
Store the IntPtr you get back from the LogonUser call and implement the GetImpersonationToken method like so:
public int GetImpersonationToken()
{
return _userToken.ToInt32();
}
Somehow the plug-in is able to tie that integer value back to a real windows account. I created a local account on the server that was in the Power Users group and used its username and password in the LogonUser method with the server being the domain. Once it is able to do so, the media should stream.
My whole IWMSAuthenticationPlugin is as follows (it uses basic authentication):
public class AuthenticationContext : IWMSAuthenticationContext
{
#region IWMSAuthenticationContext Members
private WMS_AUTHENTICATION_RESULT _result;
private readonly IWMSAuthenticationPlugin _plugin;
private Credentials _credentials;
private IntPtr _userToken;
public AuthenticationContext(IWMSAuthenticationPlugin plugin)
{
_plugin = plugin;
}
public void Authenticate(object responseBlob, IWMSContext pUserCtx, IWMSContext pPresentationCtx, IWMSCommandContext pCommandContext, IWMSAuthenticationCallback pCallback, object context)
{
// must be Unicode. If it isn't, the
// challenge isn't sent correctly
Encoding enc = Encoding.Unicode;
byte[] response;
byte[] challenge = enc.GetBytes("");
try
{
response = (byte[])responseBlob;
if (response.Length == 0)
{
// The client requested authentication; prepare the
// challenge response to send to the client. In order to
// do Basic authentication, be sure to return "Basic" from
// your implementation of IWMSAuthenticationPlugin.GetProtocolName()
string challengeTxt = "WWW-Authenticate: Basic realm=\"Domain\"";
challenge = enc.GetBytes(challengeTxt);
_result = WMS_AUTHENTICATION_RESULT.WMS_AUTHENTICATION_CONTINUE;
}
else
{
// parses Base64 encoded response and gets passed in credentials
SetCredentials(enc.GetString(response));
LdapConnection ldc = new LdapConnection("Domain");
NetworkCredential nc = new NetworkCredential(_credentials.Username, _credentials.Password, "Domain");
ldc.Credential = nc;
ldc.AuthType = AuthType.Negotiate;
ldc.Bind(nc); // user has authenticated at this point, as the credentials were used to login to the dc.
// must log in with a local windows account and get a handle for the account.
// even if success is returned, the plugin still needs a valid windows account
// to stream the file.
bool result = LogonAPI.LogonUser("local username", "local domain", "local password", LogonAPI.LOGON32_LOGON_NETWORK, LogonAPI.LOGON32_PROVIDER_DEFAULT, ref _userToken);
_result = WMS_AUTHENTICATION_RESULT.WMS_AUTHENTICATION_SUCCESS;
}
}
catch (LdapException e)
{
_result = WMS_AUTHENTICATION_RESULT.WMS_AUTHENTICATION_DENIED;
}
catch (Exception e)
{
_result = WMS_AUTHENTICATION_RESULT.WMS_AUTHENTICATION_ERROR;
}
finally
{
pCallback.OnAuthenticateComplete(_result, challenge, context);
}
}
public IWMSAuthenticationPlugin GetAuthenticationPlugin()
{
return _plugin;
}
public string GetImpersonationAccountName()
{
return "Domain\\" + _credentials.Username;
}
public int GetImpersonationToken()
{
// somehow the plugin knows how this integer ties to a windows account.
return _userToken.ToInt32();
}
public string GetLogicalUserID()
{
return GetImpersonationAccountName();
}
public void SetCredentials(string responseStr)
{
// for whatever reason, the responseStr has an extra character
// tacked on the end that blows up the conversion. When converting
// from the Base64 string, remove that last character.
string decoded = new UTF8Encoding().GetString(Convert.FromBase64String(responseStr.Substring(0, responseStr.Length - 1)));
// now that the string has been decoded and is now in the format
// username:password, parse it further into a Username and Password
// struct.
_credentials = new Credentials(decoded);
}
#endregion
}
Related
I implemented the download functionality in my android app using the download manager.
The download manager dows its job well, and once download is completed, the broadcast receiver I set is called successfully.
But, I want to monitor the download progress and display it inside my app, and not rely only on the download manager's notification.
So, I implemented a "ContentProvider and a ContentObserver" to query frequently the download manager for download progress.
Here is my content provider:
[ContentProvider(new string[] { DownloadsContentProvider.Authority })]
public class DownloadsContentProvider : ContentProvider
{
public const string Authority = "com.myapp.Myapp.DownloadProvider";
public DownloadsContentProvider()
{
}
public static Android.Net.Uri ProviderUri(long downloadId)
{
Android.Net.Uri uri = Android.Net.Uri.Parse($"http://content//downloads/my_downloads/{downloadId}");
var builder = new Android.Net.Uri.Builder()
.Authority(Authority)
.Scheme(ContentResolver.SchemeFile)
.Path(uri.Path)
.Query(uri.Query)
.Fragment(uri.Fragment);
return builder.Build();
}
public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
{
return 0;
}
public override string GetType(Android.Net.Uri uri)
{
return null;
}
public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
{
return null;
}
public override bool OnCreate()
{
return true;
}
public override ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
{
throw new NotImplementedException();
}
public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
{
return 0;
}
}
Then, I created a content observer to observe what happens and trigger the query of downloads progress.
public DownloadProgressContentObserver(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference,
transfer)
{
}
public DownloadProgressContentObserver(Handler? handler) : base(handler)
{
}
public DownloadProgressContentObserver() : base(null)
{
}
public override void OnChange(bool selfChange, Uri? uri)
{
base.OnChange(selfChange, uri);
var downloadId = uri.ToString().Substring(uri.ToString().LastIndexOf(Path.PathSeparator) + 1);
if (!string.IsNullOrEmpty(downloadId))
{
ComputeDownloadStatus(Convert.ToInt64(downloadId));
//TODO: dispatch this download percentage to the whole app, and the database
}
}
public void ComputeDownloadStatus(long downloadId)
{
long downloadedBytes = 0;
long totalSize = 0;
int status = 0;
DownloadManager.Query query = new DownloadManager.Query().SetFilterById(downloadId);
var downloadManager = DownloadManager.FromContext(Android.App.Application.Context);
var cursor = downloadManager.InvokeQuery(query);
String downloadFilePath = (cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri))).Replace("file://", "");
try
{
if (cursor != null && cursor.MoveToFirst())
{
downloadedBytes =
cursor.GetLong(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnBytesDownloadedSoFar));
totalSize =
cursor.GetInt(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnTotalSizeBytes));
}
}
finally
{
if (cursor != null)
{
cursor.Close();
}
}
var percentage = (downloadedBytes / totalSize) * 100;
}
}
This is how I use both, and register them in the download manager to monitor the download progress.
var manager = DownloadManager.FromContext(Android.App.Application.Context);
var request = new DownloadManager.Request(Android.Net.Uri.Parse(downloadUrl));
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalPublicDir(downloadsPath, fileName);
request.SetTitle(productTitle);
request.SetDescription(downloadDescription);
long downloadId = manager.Enqueue(request);
//I provide a valid URI with my content provicer
var uri = DownloadsContentProvider.ProviderUri(downloadId);
var contentResolver = Android.App.Application.Context.ContentResolver;
var observer = new DownloadProgressContentObserver();
contentResolver.RegisterContentObserver(uri, true, observer);
ProductContentObservers.Add(downloadId, observer);
I have read a lot of doc, and my implementation seems to be ok. But the content observer's "OnCHange" method is never called.
Can someone please point out what I might be doing wrong ?
I have the need (a new requirements) to perform few action before and after each method call of a bunch of classes in my project. Is a situation that somewhat reassemble a AOP but what i need is not that complex. I have some BusinessObject (class) and need that each method invokation of my API contained in that BOs will handle logging and authoriting. Because i would avoid to rewrite my codebase i have thougt to wrap my BO, in that way i can "instrument" them, but i am not sure if this is the best approach nor how to deal with some details
Example :
public class TestBo : BaseBo
{
private string test = "";
public TestBo ( string input )
{
test = input;
}
[PermissionRequired(PermissionAttribute.Login | PermissionAttribute.Read)]
public void ExampleMethod ()
{
Console.WriteLine("Run it with " + test);
}
public void WriteMessage ( string message )
{
Console.WriteLine("Write it " + message);
}
}
// IN THE BO Wrapper Class
public virtual OperationResultDto<object> Execute(Action action, Token token)
{
return this.ActionExecute(action, token);
}
public virtual OperationResultDto<object> Execute<T1>(Action<T1> action, Token token , T1 param)
{
return this.ActionExecute(action, token, param);
}
protected virtual OperationResultDto<object> ActionExecute (Delegate action, Token token, params object[] parameters)
{
try
{
if (action == null)
throw new ArgumentNullException("action");
this._logger.Debug(this.DumpParameters(action, parameters));
PermissionRequired permission = action.Method.GetCustomAttributes(false).FirstOrDefault(attr => attr is PermissionRequired) as PermissionRequired;
//TODO : authorization;
action.DynamicInvoke(parameters);
this._logger.Debug($"Invokation {action.Method.Name} ended");
return new OperationResultDto<object>() { Success = true, ReturnData = null };
}
catch (Exception err)
{
this._logger.Error(err, string.Empty);
return new OperationResultDto<object>()
{
Success = false,
ErrorText = err.Message,
Error = err,
ReturnData = null
};
}
return null;
}
//USAGE
TestBo testBo = new TestBo("Identity");
testBo.ExampleMethod();
Token token = new Token(1);
BoWrapper wrapper = new BoWrapper(testBo);
wrapper.Execute(testBo.ExampleMethod, token);
wrapper.Execute<string>(testBo.WriteMessage, token, "Message");
That seems to work as intended, but i don't like that :
wrapper.Execute<string>.. //I have to repeat method signature each time
public virtual OperationResultDto<object> Execute<T1>(Action<T1>..
//I have to write many overload (in BoWrapper) to handle all possible situation.
I've searched on google on how to dynamically edit the Resources.Values.strings.xml file so I can add my Facebook Access Token so the user won't need to log in again when he reuses the app.
Is it possible to edit it, or do I have to use another method to store the token?
I think the best place would be to save this to the KeyStore. To do this you must use the DependencyService More Info here
The interface and implementation would then be:
PCL interface
public interface IAuth
{
void CreateStore();
IEnumerable<string> FindAccountsForService(string serviceId);
void Save(string pin,string serviceId);
void Delete(string serviceId);
}
Android
public class IAuthImplementation : IAuth
{
Context context;
KeyStore ks;
KeyStore.PasswordProtection prot;
static readonly object fileLock = new object();
const string FileName = "MyProg.Accounts";
static readonly char[] Password = null;
public void CreateStore()
{
this.context = Android.App.Application.Context;
ks = KeyStore.GetInstance(KeyStore.DefaultType);
prot = new KeyStore.PasswordProtection(Password);
try
{
lock (fileLock)
{
using (var s = context.OpenFileInput(FileName))
{
ks.Load(s, Password);
}
}
}
catch (Java.IO.FileNotFoundException)
{
//ks.Load (null, Password);
LoadEmptyKeyStore(Password);
}
}
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var r = new List<string>();
var postfix = "-" + serviceId;
var aliases = ks.Aliases();
while (aliases.HasMoreElements)
{
var alias = aliases.NextElement().ToString();
if (alias.EndsWith(postfix))
{
var e = ks.GetEntry(alias, prot) as KeyStore.SecretKeyEntry;
if (e != null)
{
var bytes = e.SecretKey.GetEncoded();
var password = System.Text.Encoding.UTF8.GetString(bytes);
r.Add(password);
}
}
}
return r;
}
public void Delete(string serviceId)
{
var alias = MakeAlias(serviceId);
ks.DeleteEntry(alias);
Save();
}
public void Save(string pin, string serviceId)
{
var alias = MakeAlias(serviceId);
var secretKey = new SecretAccount(pin);
var entry = new KeyStore.SecretKeyEntry(secretKey);
ks.SetEntry(alias, entry, prot);
Save();
}
void Save()
{
lock (fileLock)
{
using (var s = context.OpenFileOutput(FileName, FileCreationMode.Private))
{
ks.Store(s, Password);
}
}
}
static string MakeAlias(string serviceId)
{
return "-" + serviceId;
}
class SecretAccount : Java.Lang.Object, ISecretKey
{
byte[] bytes;
public SecretAccount(string password)
{
bytes = System.Text.Encoding.UTF8.GetBytes(password);
}
public byte[] GetEncoded()
{
return bytes;
}
public string Algorithm
{
get
{
return "RAW";
}
}
public string Format
{
get
{
return "RAW";
}
}
}
static IntPtr id_load_Ljava_io_InputStream_arrayC;
void LoadEmptyKeyStore(char[] password)
{
if (id_load_Ljava_io_InputStream_arrayC == IntPtr.Zero)
{
id_load_Ljava_io_InputStream_arrayC = JNIEnv.GetMethodID(ks.Class.Handle, "load", "(Ljava/io/InputStream;[C)V");
}
IntPtr intPtr = IntPtr.Zero;
IntPtr intPtr2 = JNIEnv.NewArray(password);
JNIEnv.CallVoidMethod(ks.Handle, id_load_Ljava_io_InputStream_arrayC, new JValue[]
{
new JValue (intPtr),
new JValue (intPtr2)
});
JNIEnv.DeleteLocalRef(intPtr);
if (password != null)
{
JNIEnv.CopyArray(intPtr2, password);
JNIEnv.DeleteLocalRef(intPtr2);
}
}
Call Create Store in the main activity of Android app first. - This could possibly be improved and remove CreateStrore() from the interface by checking if ks == null in Save and Delete and calling the method if true
This would then save the access token to the KeyStore that you can then retrieve later
I have an object I want to store in the IsolatedStorageSettings, which I wan't to reuse when the application restarts.
My problem lies in that the code I have written for some reason does not remember the object when trying to access the key upon restarting it.
namespace MyNameSpace
{
public class WindowsPhoneSettings
{
private const string SelectedSiteKey = "SelectedSite";
private IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
private T RetrieveSetting<T>(string settingKey)
{
object settingValue;
if (isolatedStore.TryGetValue(settingKey, out settingValue))
{
return (T)settingValue;
}
return default(T);
}
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
if (isolatedStore.Contains(Key))
{
if (isolatedStore[Key] != value)
{
isolatedStore[Key] = value;
valueChanged = true;
}
}
else
{
isolatedStore.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public MobileSiteDataModel SelectedSite
{
get
{
return RetrieveSetting<MobileSiteDataModel>(SelectedSiteKey);
}
set
{
AddOrUpdateValue(SelectedSiteKey, value);
isolatedStore.Save();
}
}
}
}
I then instantiate WindowsPhoneSettings in App.xaml.cs and make a public getter and setter for it. To be able to access it in the whole application. Debugging this shows that the right object gets stored in the isolated store, but when closing the app and reopening it isolated store seems to be empty. I have tried this on both the emulator and a real device. As you can see I do call the save method when setting the object.
What am I doing wrong here?
I ended up saving the settings to a file in the isolated storage as IsolatedStorageSettings never seemed to work.
So my code ended up like this:
public class PhoneSettings
{
private const string SettingsDir = "settingsDir";
private const string SettingsFile = "settings.xml";
public void SetSettings(Settings settings)
{
SaveSettingToFile<Settings>(SettingsDir, SettingsFile, settings);
}
public Settings GetSettings()
{
return RetrieveSettingFromFile<Settings>(SettingsDir, SettingsFile);
}
private T RetrieveSettingFromFile<T>(string dir, string file) where T : class
{
IsolatedStorageFile isolatedFileStore = IsolatedStorageFile.GetUserStoreForApplication();
if (isolatedFileStore.DirectoryExists(dir))
{
try
{
using (var stream = new IsolatedStorageFileStream(System.IO.Path.Combine(dir, file), FileMode.Open, isolatedFileStore))
{
return (T)SerializationHelper.DeserializeData<T>(stream);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Could not retrieve file " + dir + "\\" + file + ". With Exception: " + ex.Message);
}
}
return null;
}
private void SaveSettingToFile<T>(string dir, string file, T data)
{
IsolatedStorageFile isolatedFileStore = IsolatedStorageFile.GetUserStoreForApplication();
if (!isolatedFileStore.DirectoryExists(dir))
isolatedFileStore.CreateDirectory(dir);
try
{
string fn = System.IO.Path.Combine(dir, file);
if (isolatedFileStore.FileExists(fn)) isolatedFileStore.DeleteFile(fn); //mostly harmless, used because isolatedFileStore is stupid :D
using (var stream = new IsolatedStorageFileStream(fn, FileMode.CreateNew, FileAccess.ReadWrite, isolatedFileStore))
{
SerializationHelper.SerializeData<T>(data, stream);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Could not save file " + dir + "\\" + file + ". With Exception: " + ex.Message);
}
}
}
And a settings class just containing the stuff I want to save. This could be:
class Settings
{
private string name;
private int id;
public string Name
{
get { return name; }
set { name = value; }
}
public int Id
{
get { return id; }
set { id = value; }
}
}
EDIT: Sample of how SerializationHelper could be implemented
public static class SerializationHelper
{
public static void SerializeData<T>(this T obj, Stream streamObject)
{
if (obj == null || streamObject == null)
return;
var ser = new DataContractJsonSerializer(typeof(T));
ser.WriteObject(streamObject, obj);
}
public static T DeserializeData<T>(Stream streamObject)
{
if (streamObject == null)
return default(T);
var ser = new DataContractJsonSerializer(typeof(T));
return (T)ser.ReadObject(streamObject);
}
}
Objects stored in IsolatedStorageSettings are serialised using the DataContractSerializer and so must be serializable. Ensure they can be or serialize (and deserialize) them yourself before adding to (and after removing from) ISS.
If the items aren't there when trying to retrieve then it may be that they couldn't be added in the first place (due to a serialization issue).
Here is the code I use to save an object to isolated storage and to load an object from isolated storage -
private void saveToIsolatedStorage(string keyname, object value)
{
IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
isolatedStore.Remove(keyname);
isolatedStore.Add(keyname, value);
isolatedStore.Save();
}
private bool loadObject(string keyname, out object result)
{
IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
result = null;
try
{
result = isolatedStore[keyname];
}
catch
{
return false;
}
return true;
}
Here is code I use to call the above -
private void SaveToIsolatedStorage()
{
saveToIsolatedStorage("GameData", GameData);
}
private void LoadFromIsolatedStorage()
{
Object temp;
if (loadObject("GameData", out temp))
{
GameData = (CGameData)temp;
}
else
{
GameData.Reset();
}
}
Note that the objects I save and restore like this are small and serializable. If my object contains a 2 dimensional array or some other object which is not serializable then I perform my own serialization and deserialization before using iso storage.
What if you changed RetrieveSetting<T> to this:
private T RetrieveSetting<T>(string settingKey)
{
T settingValue;
if(isolatedStore.TryGetValue(settingKey, out settingValue))
{
return (T)settingValue;
}
return default(T);
}
Notice that the object being fetched is being declared as type T instead of object.
within windows live messenger, it is possible to share the song you are currently listening to. what would i need to do to get this working within c# like libarys etc cannot find the correct documentation on google.
You'll need to use the iTunes SDK to interact with iTunes from .NET. So there's your Google search term. :)
Here's a start:
http://blogs.msdn.com/b/noahc/archive/2006/07/06/automating-itunes-with-c-in-net.aspx
http://blogs.msdn.com/b/dancre/archive/2004/05/08/128645.aspx
Here is a script for LinqPad in C# which does as requested. (see LinqPad.com)
Bonus! Artwork view.
It looks like this:
<Query Kind="Program">
<Namespace>iTunesLib</Namespace>
<Namespace>System.Security.Cryptography</Namespace>
</Query>
void Main()
{
var track = new iTunesApp().CurrentTrack;
if (track == null)
"nothing playing".Dump();
else
new Viewer(track,true).Dump();
}
public class Viewer
{
const string PREFIX = "itlps-";
private IITFileOrCDTrack store;
private bool materialize;
public string album { get { return store.Album; } }
public string band { get { return store.Artist; } }
public string song { get { return store.Name; } }
public string desc { get { return store.Description; } }
public int? artCnt { get {
if (store.Artwork == null) return null;
else return store.Artwork.Count; }
}
public IEnumerable<ImageViewer> art { get {
if (materialize)
{
foreach(var artT in store.Artwork)
{
var art = artT as IITArtwork;
string ext = ".tmp";
switch(art.Format)
{
case ITArtworkFormat.ITArtworkFormatBMP:
ext = ".BMP";
break;
case ITArtworkFormat.ITArtworkFormatJPEG:
ext = ".JPG";
break;
case ITArtworkFormat.ITArtworkFormatPNG:
ext = ".PNG";
break;
}
string path = Path.Combine(Path.GetTempPath(),PREFIX+Path.GetRandomFileName()+ext);
art.SaveArtworkToFile(path);
yield return new ImageViewer(path);
}
}
yield break; }
}
public Viewer(IITFileOrCDTrack t,bool materializeArt = false)
{
store = t;
materialize = materializeArt;
}
public Viewer(IITTrack t,bool materializeArt = false)
{
store = t as IITFileOrCDTrack;
materialize = materializeArt;
}
}
public class ImageViewer
{
public string hash { get { return _hash.Value; } }
static private string _path { get; set; }
public object image { get { return _image.Value; } }
static private SHA1Managed sha = new SHA1Managed();
private Lazy<object> _image = new Lazy<object>(() => {return Util.Image(_path);});
private Lazy<string> _hash = new Lazy<string>(() =>
{
string hash = string.Empty;
using (FileStream stream = File.OpenRead(_path))
{
byte [] checksum = sha.ComputeHash(stream);
hash = BitConverter.ToString(checksum).Replace("-", string.Empty);
}
return hash;
});
public ImageViewer(string path)
{
_path = path;
}
}
last i checked this functionality is included out of the box all you need is to have itunes and windows live messenger installed and activate "what im listening to" and it shows this in your messenger status. if you are looking to create a bot that messages this out to a contact that is a different story tho that you will need to write a script for