I've tried various techniques but can't work out why this code always results in a valid(active?) license, even if the user hasn't bought the app add-on.
The function initialises when the app starts.
public async void GetLicenseInfo()
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
workingProgressRing.IsActive = true;
StoreAppLicense appLicense = await context.GetAppLicenseAsync();
workingProgressRing.IsActive = false;
if (appLicense == null)
{
messagetextblock.Text = "An error occurred while retrieving the license.";
return;
}
// Use members of the appLicense object to access license info...
// the customer can' t access this feature
messagetextblock.Text = "customer hasn't bought the addon";
// Access the valid licenses for durable add-ons for this app.
foreach (KeyValuePair<string, StoreLicense> item in appLicense.AddOnLicenses)
{
StoreLicense addOnLicense = item.Value;
// Use members of the addOnLicense object to access license info
// for the add-on.
// Specify the kinds of add-ons to retrieve.
string[] productKinds = { "Durable", "Consumable", "UnmanagedConsumable" };
List<String> filterList = new List<string>(productKinds);
StoreProductQueryResult queryResult = await context.GetAssociatedStoreProductsAsync(filterList);
if (addOnLicense.IsActive)
{
// the customer can access this feature
messagetextblock.Text = "customer has bought the addon";
}
}
}
It's hard to tell exactly what could be wrong with your above example. However, I have an app in the market that uses two different types of in app purchases. One to remove ads and one that gets a premium license.
The difference between how you're doing it and I'm doing it is I'm looking for subscription store id in my license skues that are returned with the license.
have you tried this method yet?
public static void CheckForPremiumStatus()
{
#if DEBUG
if (LicenseInformation.ProductLicenses["PremiumStatus"].IsActive)
{
IsPremium = RemoveAds = true;
}
#else
var subscriptionStoreId = "9PMT47KC5W6C";
foreach (var addOnLicense in _appLicense.AddOnLicenses)
{
StoreLicense license = addOnLicense.Value;
if (license.SkuStoreId.StartsWith(subscriptionStoreId))
{
if (license.IsActive)
{
IsPremium = RemoveAds = true;
return;
}
else
{
break;
}
}
}
IsPremium = RemoveAds = false;
#endif
}
You can see the full example here - feel free to copy and paste it if you want. Just swap out the addon store id's
Related
I am using System.DirectoryServices.AccountManagement.GroupPrincipal FindByIdentity in C# to create an object containing the group members (user IDs and names) for the target group. My goal is to iterate through the resulting list of UserPrincipals and print the SamAccountName and DisplayName for each. For some target groups, this is working fine; for others it fails on a user (or perhaps more than one) that throws the following error:
System.DirectoryServices.AccountManagement.PrincipalOperationException
HResult=0x80131501
Message=The specified directory service attribute or value does not exist.
When I use PowerShell’s Get-ADGroup to get the group object for one of the failing targets and iterate through it, there is no problem.
I’ve looked into the AD Group memberships and I believe the problem is that in some groups (those failing), some members may have been disabled, or may be part of a cross-domain trust. However, their status is of no consequence to me; I just want to list everything so the group owner can decide which members get migrated to new groups.
The method I am using is:
private static ArrayList EnumerateGroupMembers()
{
ArrayList gmObjects = new ArrayList();
string ldapVal = "DC=dc1,DC=dc2,DC=dcMain,DC=dcSecondary";
string ldapDom = "dc1.dc2.dcMain.dcSecondary:389";
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, ldapDom, ldapVal);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "AD-GROUPNAME");
if (group != null)
{
var users = group.GetMembers(true);
//*** PrincipalOperationException occurs here ***
foreach (UserPrincipal p in users)
{
Console.WriteLine(p.SamAccountName + ", " + p.DisplayName);
}
Console.WriteLine("Done");
Console.ReadKey();
}
//*** Please note: I know I am returning an empty list here. I'm writing to Console during development
return gmObjects;
}
Can anyone suggest how I can iterate through the list of UserPrincipals without throwing a PrincipalOperationException? Or, at least a way to bypass the UserPrincipal occurrences that are causing these errors? Even if I cannot list the failing users I will survive.
Unfortunately, the System.DirectoryServices.AccountManagement namespace does not play nicely with Foreign Security Principals, as you've found.
You can do it using the System.DirectoryServices namespace, which is what AccountManagement uses behind the scenes. You will probably find it performs better anyway, although it is a little bit more complicated.
I've been meaning to write up something like this for my website anyway, so here is a method that will find all members of a group and list them in DOMAIN\username format. It has the option to expand nested groups too.
public static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false, Dictionary<string, string> domainSidMapping = null) {
var members = new List<string>();
group.RefreshCache(new[] { "member", "canonicalName" });
if (domainSidMapping == null) {
//Find all the trusted domains and create a dictionary that maps the domain's SID to its DNS name
var groupCn = (string) group.Properties["canonicalName"].Value;
var domainDns = groupCn.Substring(0, groupCn.IndexOf("/", StringComparison.Ordinal));
var domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domainDns));
var trusts = domain.GetAllTrustRelationships();
domainSidMapping = new Dictionary<string, string>();
foreach (TrustRelationshipInformation trust in trusts) {
using (var trustedDomain = new DirectoryEntry($"LDAP://{trust.TargetName}")) {
try {
trustedDomain.RefreshCache(new [] {"objectSid"});
var domainSid = new SecurityIdentifier((byte[]) trustedDomain.Properties["objectSid"].Value, 0).ToString();
domainSidMapping.Add(domainSid, trust.TargetName);
} catch (Exception e) {
//This can happen if you're running this with credentials
//that aren't trusted on the other domain or if the domain
//can't be contacted
Console.WriteLine($"Can't connect to domain {trust.TargetName}: {e.Message}");
}
}
}
}
while (true) {
var memberDns = group.Properties["member"];
foreach (string member in memberDns) {
using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
memberDe.RefreshCache(new[] { "objectClass", "msDS-PrincipalName", "cn" });
if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true, domainSidMapping));
} else if (memberDe.Properties["objectClass"].Contains("foreignSecurityPrincipal")) {
//User is on a trusted domain
var foreignUserSid = memberDe.Properties["cn"].Value.ToString();
//The SID of the domain is the SID of the user minus the last block of numbers
var foreignDomainSid = foreignUserSid.Substring(0, foreignUserSid.LastIndexOf("-"));
if (domainSidMapping.TryGetValue(foreignDomainSid, out var foreignDomainDns)) {
using (var foreignUser = new DirectoryEntry($"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>")) {
foreignUser.RefreshCache(new[] { "msDS-PrincipalName" });
members.Add(foreignUser.Properties["msDS-PrincipalName"].Value.ToString());
}
} else {
//unknown domain
members.Add(foreignUserSid);
}
} else {
var username = memberDe.Properties["msDS-PrincipalName"].Value.ToString();
if (!string.IsNullOrEmpty(username)) {
members.Add(username);
}
}
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
There are a few things this has to do:
The member attribute will only give you 1500 accounts at a time, so you have to ask for more until there are none left.
Foreign Security Principal's have the SID of the account on the foreign domain, but you need to use the domain's DNS name to connect to it (i.e. $"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>"). So this method will look up all the trusts for the domain and create a mapping between the domain's SID and its DNS name.
You use it like this:
var group = new DirectoryEntry($"LDAP://{distinguishedNameOfGroup}");
var members = GetGroupMemberList(group);
Or you can use GetGroupMemberList(group, true) if you want to find users in nested groups too.
Keep in mind that this will not find users who have this group as their primary group, since the primary group does not use the member attribute. I describe that in my What makes a member a member article. In most cases you won't care though.
When performing an in-app purchase from a WPF Desktop Application using StoreContext.RequestPurchaseAsync() from the namespace Windows.services.Store, we get a StorePurchaseResult with an ExtendedError message "Value does not fall within the expected range."
Our application is published and available for download from the Windows Store.
It was converted using the DesktopAppConverter tool. We set the manifest.appx according to the description in the Store (Identity Name, Publisher...).
We followed the instructions provided below for using in-app purchase in C# from the UI thread of a Windows desktop application that uses the Desktop Bridge.
https://learn.microsoft.com/en-us/windows/uwp/monetize/in-app-purchases-and-trials
https://learn.microsoft.com/en-us/windows/uwp/monetize/enable-in-app-purchases-of-apps-and-add-ons
In our app's code, we declare the IInitializeWithWindow interface :
[ComImport]
[Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
Then, when our application is starting, we get the StoreContext (stored into the storeContext_ attribute), using the single-user way :
// Init the store context
storeContext_ = StoreContext.GetDefault();
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)storeContext_;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
After this, we manage to retreive the StoreLicense (stored into the storeLicense_ attribute) and list the associated store products without any error
// Get the current user license
storeLicense_ = await storeContext_.GetAppLicenseAsync();
if (storeLicense_ == null)
{
MessageBox.Show("An error occurred while retrieving the license.", "StoreLicenseApp Error");
return;
}
// Create a filtered list of the product AddOns I care about
string[] filterList = new string[] { "Durable" };
// Get list of Add Ons this app can sell, filtering for the types we know about
StoreProductQueryResult addOns = await storeContext_.GetAssociatedStoreProductsAsync(filterList);
if (addOns.ExtendedError != null)
{
MessageBox.Show("Impossible to retreive the list of the products on the store.\n" + addOns.ExtendedError.Message,
"Get Associated Store Products Error");
}
Once the store ids of the products are retreived from the store, we wait for the user to click on a purchase button that calls the callback below.
private async void PurchaseButton_Clicked(object sender, RoutedEventArgs e)
{
StorePurchaseResult result = await storeContext_.RequestPurchaseAsync(selectedStoreId);
// Capture the error message for the operation, if any.
string extendedError = string.Empty;
if (result.ExtendedError != null)
{
extendedError = result.ExtendedError.Message;
}
[...]
}
RequestPurchaseAsync() return an error instead of showing the Metro interface for purchasing the product.
Here is the extended error returned :
Message = "Value does not fall within the expected range."
HResult = -2147024809
Any clue of how to fix this issue ?
The RequestPurchaseAsync() called in our PurchaseButton_Clicked() function was returning an error because we was initializing the store context in another function when the application was starting.
We solved the issue by placing the store context initializing instructions in the PurchaseButton_Clicked() as follow :
public async void PurchaseSelectedProduct(ProductModel product){
if (product == null){
MessageBox.Show("Product not valid", "PurchaseInApp Error");
return;
}
// Init store context for purchase
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)storeContext_;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
// Buy through in app purchase sdk
StorePurchaseResult result = null;
try { result = await product.StoreProduct.RequestPurchaseAsync(); }
}
I have this code which makes a call to Facebook API in order to get the mutual friends on the application for two given users. The parameter nextPage corresponds to facebook api's next page.
Problem is that although I have a limited number of common friends each time, I get errors from Facebook api, for having too many calls per second (about 5 milion/day overall). I tried mocking the call to facebook, to always return null, but that resulted in a CPU overloading due to w3p, with 99% usage. What am I missing?
public async Task<MutualFriendsModel> GetMutualFriends(Guid currentUserGuid,
Guid visitedUserGuid, string nextPage)
{
var currentUser = Get(currentUserGuid);
var visitedUser = _serviceUserLogin.GetByUserId(visitedUserGuid);
var mutualFriends = new MutualFriendsModel();
var hasNextPage = true;
while (hasNextPage && mutualFriends.Users.Count < 25)
{
var facebookResult = await
_facebookApi.GetMutualFriendsFacebookRequest(currentUser.Token,
visitedUser.ProviderKey, nextPage);
if (facebookResult == null) break;
mutualFriends.Update(facebookResult, this, _serviceUserLogin);
nextPage = mutualFriends.NextPageUrl;
hasNextPage = !string.IsNullOrEmpty(mutualFriends.NextPageUrl);
}
return mutualFriends;
}
Also, there is another variation of the above mentioned snippet, which only counts the mutual friends.
private async Task<IList<SavedCommentModel>> MutualFriendsCount(User currentUser,
IList<SavedCommentModel> comments)
{
var usersFriends = new Dictionary<Guid, long>();
foreach (var comment in comments)
{
if (usersFriends.ContainsKey(comment.UserId))
{
comment.TotalMutualFriends = usersFriends[comment.UserId];
}
else
{
var visitedUser = _serviceUserLogin.GetByUserId(comment.UserId);
if (visitedUser.LoginProvider != LoginProvider.Facebook.ToString()) continue;
var facebookResult = await
_facebookApi.GetMutualFriendsFacebookRequest(currentUser.Token, visitedUser.ProviderKey);
if (facebookResult == null) continue;
comment.TotalMutualFriends = facebookResult.Context.Mutual_friends.Summary.Total_Count;
usersFriends.Add(comment.UserId, comment.TotalMutualFriends);
}
}
return comments;
}
I'm experimenting with the new WinRT Appointments API in Windows 8.1, based on a sample provided on the MSDN website of Microsoft: http://code.msdn.microsoft.com/windowsapps/Appointments-API-sample-2b55c76e
It works great and I can add appointments without a hassle, but there's always a confirmation by the user involved when using the method ShowAddAppointmentAsync from the Windows.ApplicationModel.Appointments.AppointmentManager namespace, which shows the Appointments provider Add Appointment UI.
I'm looking for a solution to add a larger collection of appointments in the default Windows 8 calendar, WITHOUT the confirmation for each individual appointment in the collection. Is there a way to get around this and bulk insert appointments? Maybe the Windows Live SDK?
Its true that the API prompts the user before saving, but there is a provision to achieve this.
var appointment = new Windows.ApplicationModel.Appointments.Appointment();
appointment.details = "This is a dummy appointment";
appointment.reminder = 15000;
appointment.subject = "TEST APPPOINTMENT";
var x = new Windows.ApplicationModel.Appointments.AppointmentManager.requestStoreAsync(Windows.ApplicationModel.Appointments.AppointmentStoreAccessType.appCalendarsReadWrite).done(function (apppointmentStore) {
apppointmentStore.createAppointmentCalendarAsync("TEST CALENDAR").done(function (calendar) {
calendar.saveAppointmentAsync(appointment);
});
})
Here you're an example to do it using C#
private AppointmentCalendar currentAppCalendar;
private AsyncLazy<AppointmentStore> lazyAppointmentStore = new AsyncLazy<AppointmentStore>(async () =>
{
var appStore = await AppointmentManager.RequestStoreAsync(AppointmentStoreAccessType.AppCalendarsReadWrite);
return appStore;
});
private AppointmentStore AppStore { get { return lazyAppointmentStore.Value.Result; } }
public AppointmentService()
{
}
public async Task CreateCalendar()
{
IReadOnlyList<AppointmentCalendar> appCalendars =
await AppStore.FindAppointmentCalendarsAsync(FindAppointmentCalendarsOptions.IncludeHidden);
AppointmentCalendar appCalendar = null;
// Apps can create multiple calendars. Here app creates only one.
if (appCalendars.Count == 0)
{
appCalendar = await AppStore.CreateAppointmentCalendarAsync(Constants.CalendarName);
}
else
{
appCalendar = appCalendars[0];
}
appCalendar.OtherAppReadAccess = AppointmentCalendarOtherAppReadAccess.Full;
appCalendar.OtherAppWriteAccess = AppointmentCalendarOtherAppWriteAccess.SystemOnly;
// This app will show the details for the appointment. Use System to let the system show the details.
appCalendar.SummaryCardView = AppointmentSummaryCardView.App;
await appCalendar.SaveAsync();
currentAppCalendar = appCalendar;
}
public async Task<bool> CreateNewAppointment(Data.Schemas.Task task)
{
if (null == task)
throw new ArgumentNullException("task");
Appointment newAppointment = new Appointment();
this.SaveAppointmentData(task, newAppointment);
try
{
// Show system calendar to the user to be edited
string appointmentId = await AppointmentManager.ShowAddAppointmentAsync(newAppointment, Windows.Foundation.Rect.Empty);
return ! string.IsNullOrWhiteSpace(appointmentId);
// Just save the appointment
// await currentAppCalendar.SaveAppointmentAsync(newAppointment);
// return true;
}
catch
{
return false;
}
}
Check my post, to know more about AsyncLazy.
I hope this help you.
Regards.
Juanlu
This is not possible, by using the WinRT appointments API.
A user interaction is always required. It was a design decision by MS that some actions require user interaction and this is one of it.
As stated by #Ken Tucker, you can use the windows live api to create appointments but this requires the user of your app to sing in to windows live and grat it the required permissions.
I'm developing a "Task Control System" that will allow its users to enter task description information including when to execute the task and what environment (OS, browser, etc.) the task requires.
The 'controller' saves the description information and schedules the task. When the scheduled time arrives, the scheduler retrieves the task information and 'queues' the task for a remote machine that matches the required environment.
My first cut at this used a relational database to persist the task descriptions and enough history information to track problems (about 2 weeks worth). But this is not a 'big data' problem and the relationships are simple and I need better performance.
So I'm looking for something that offers more performance.
I'm trying to use redis for this, but I'm having some problems. I'm using ServiceStack.Redis version 3.9.71.0 for the client and Redis 2.8.4 is the server.
This sample code is taken from Dan Swain's tutorial. It's updated to work with ServiceStack.Redis client v 3.9.71.0. Much of it works, but 'currentShippers.Remove(lameShipper);' does NOT work.
Can anyone see why that might be?
Thanks
public void ShippersUseCase()
{
using (var redisClient = new RedisClient("localhost"))
{
//Create a 'strongly-typed' API that makes all Redis Value operations to apply against Shippers
var redis = redisClient.As<Shipper>();
//Redis lists implement IList<T> while Redis sets implement ICollection<T>
var currentShippers = redis.Lists["urn:shippers:current"];
var prospectiveShippers = redis.Lists["urn:shippers:prospective"];
currentShippers.Add(
new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "Trains R Us",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.Trains,
UniqueRef = Guid.NewGuid()
});
currentShippers.Add(
new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "Planes R Us",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.Planes,
UniqueRef = Guid.NewGuid()
});
var lameShipper = new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "We do everything!",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.All,
UniqueRef = Guid.NewGuid()
};
currentShippers.Add(lameShipper);
Dump("ADDED 3 SHIPPERS:", currentShippers);
currentShippers.Remove(lameShipper);
.
.
.
}
}
Fixed the problem by adding these overrides to the 'Shipper' class:
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
var input = obj as Shipper;
return input != null && Equals(input);
}
public bool Equals(Shipper other)
{
return other != null && (Id.Equals(other.Id));
}
public override int GetHashCode()
{
return (int)Id;
}
This working example shows how to implement List<>.Contains, List<>.Find, and List<>.Remove. Once applied to the 'Shipper' class the problem was solved!