RSS

How do I - Create a SharePoint 2013 sub site template when the UI option is not there?


SharePoint's publishing infrastructure and Features have been the bane of many a debug session I've been involved in.  SharePoint sites just behave differently when publishing is turned on.  Along with this, comes some menu and settings differences that Microsoft enforces for publishing sites.  Good, bad or indifferent, these nuances require some unique workarounds from time to time.

I have a client who is using publishing as part of their base site template.  Now the client wants to create a site template from a given sub site.  The template would then be used to create other sub sites in the same site collection.  Without publishing, this is no problem as you simply navigate to the Settings Gear > Site Settings > Site Actions > Save as template.  When publishing is turned on however, this option disappears from the settings page.
No problem you think.  I'm a smart and attentive SharePoint guru and I know what the "Save as template" page's URL is.  I'll just manually type it into the browser's address bar.  So you proceed to manually add _layouts/15/savetmpl.aspx to the current site URL so it would read something like this:
http://www.crayveon.com/sites/PublishingSite1/SubSiteA/_layouts/15/savetmpl.aspx
With confidence you press the key on your keyboard and...

















This is one of those little settings Microsoft made while trying to "protect" you from yourself.  OK, so how do we work around this one?
It's actually pretty easy once you know how the underlying process functions.  The "savetmpl.aspx" page does a lookup at the site property bag to see if there's a specific variable defined.  The variable is called "SaveSiteAsTemplateEnabled".  If it's defined, its value is checked and if set to "false", then the above error message is thrown and the template save process is aborted.
That said, we can work around this by using SharePoint Designer to change the variable value.  Here are the complete steps to overcome this little problem:
  1. Open SharePoint Designer 2013. 
  2. Open your target site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA  
  3. In the ribbon at the top, on the far right, click "Site Options". 
  4. On the Site Options dialog, under the "Parameters" tab, you will notice the target variable in question... "SaveSiteAsTemplateEnabled".  Its value should read "false".  Select the variable and click the "Modify" button on the right. 
  5. Change the value to "true" and click "OK". 
  6. Click "OK" to close the Site Options dialog window.
  7. From your browser, navigate to the target site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA
  8. From the target site, add "/_layouts/15/savetmpl.aspx" to the URL of the site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA/_layouts/15/savetmpl.aspx and press Enter.
  9. You should be presented with the Save as Template page. 
  10. Fill out your desired options and click "OK" to save the template.
Provided you don't have any custom apps on the site that are not compatible with templatization, the result should be successful.  If you do have custom apps, you'll have to remove said apps and then try saving as a template again.

Enjoy!
C




How do I - Exclude certain file types from a XCopy folder sync?


When you're managing multiple SharePoint farms that rely on custom, in-house developed Powershell modules, keeping these modules up to date across the entire enterprise can be a tricky prospect.  This is usually where I employ some Powershell with calls to XCopy.

XCopy is a very powerful tool for synching files between systems.  As you can see from its documentation page, there are many command switches for it.  The tricky thing is usually when you're trying to sync folders that contain Powershell scripts, but these scripts generate log files.  You want to sync the .ps1 files, but not the .log files.  How do we do that then?

This is where the /exclude: switch comes into play.  You can create a text file containing the extensions you wish to exclude.  Then you simply pass the text file to the /exclude: switch thus:

xcopy S:\Corne\Scripts\*.* T:\Scripts /E /I /F /H /R /Y /d /exclude:Exclusions.txt

Its important to remember that the exclusion will do a simple path match on the values specified in exclusions.txt.  As such, having the file content defined thus:

.log

will exclude anything that matches ".log" somewhere in the path of the file e.g. files ending in ".logger", ".logs", ".log2, ".log" etc. will all be excluded from XCopy.  This can be resolved by simply adding a backslash to the back of the extension thus:

.log\

Now XCopy will only ignore files with a ".log" extension.

Enjoy
C

i-SharePoint anyone?


This has been sorely missing for years! Thankfully Microsoft is finally making this happen.
Microsoft brings SharePoint to iOS https://techcrunch.com/2016/06/21/microsoft-brings-sharepoint-to-ios

Exciting times.
Later
C

Fixing Camtasia Studio black preview screen problem in Windows 10


Powershell Tip - Padding Text


Since we use Powershell for just about everything these days, you're sure to end up generating some kind of quick report of something in SharePoint through a simple Powershell script.  Inevitably, the need to pad text comes up.  Powershell makes it easy to left and right pad text variables through the use of the .PadLeft() and .PadRight() .NET methods used as follows:

$str = "My Report Title";
$strPad = $str.PadLeft(20) + ".";
Write-Host $strPad;

The output looks like this:

     My Report Title.

In this case, we were left padding the string to 20 characters.  By default, padding is done with spaces so given the string was already 15 characters long, 5 spaces were added to the front of the string resulting in our output.  The use of the additional period will become apparent as we look at right padding the text as follows:

$strPad = $str.PadRight(20) + ".";
Write-Host $strPad;

The output looks like this:

My Report Title     .

Here you can clearly see the 5 spaces before the period. ;-)
If you want to pad with something other than spaces, you can simply supply the second, optional parameter to PadLeft() or PadRight() specifying the character to be used for padding e.g.

$strPad = $str.PadRight(20, "$") + ".";
Write-Host $strPad;

The output looks like this:

My Report Title$$$$$.

Enjoy!
C


How do I - Round numbers to a given decimal space in Powershell?


One of my subscribers asked how to round in Powershell.  It's actually quite easy.  All you have to do is use the [math] preprocessor's Round() method e.g. if I wanted to round to two decimal places I'll use the following:

[math]::Round($value, 2);

If I issue the Round() call without a second parameter thus:

[math]::Round($value);

I would get an integer rounded value, i.e. I'd get the whole, ignoring any decimals.

Hope that helps!

Later
C

How do I use Powershell with IIS 7 or later?


If you're into any form of automation, you know that Powershell rocks!
That said, the real strength of Powershell comes from it's snap-ins.  These extensions allow you to do all manner of things.  One of those is the ability to script actions in IIS.  To get started you'll need to ensure you have the Feature installed on your flavor of Windows.  If you don't have it installed, you can either download it here:

https://www.microsoft.com/en-us/download/details.aspx?id=7436

or add it via Windows' Programs and Features.  Generally on anything late Windows 7 or Server 2008, it's already loaded.

Once installed, you'll need to load the snap-in thus:

Import-Module WebAdministration
From here, you're only limited by the available calls and your imaginations.

Happy scripting.
C

What is the SharePoint hive?


Almost a decade ago I posted an article explaining what the 60/12 hive is.  Since then, SharePoint has undergone many new versions so I thought it was time I updated my hive article just a bit to include the three additional major releases since then.  For more information on the origination of the "hive", refer to the original article linked above.

SharePoint Version
Hive Server Disk Location
2007C:\Program Files\Common Files\Microsoft Shared\web server extensions\12
2010C:\Program Files\Common Files\Microsoft Shared\web server extensions\14
2013C:\Program Files\Common Files\Microsoft Shared\web server extensions\15
2016C:\Program Files\Common Files\Microsoft Shared\web server extensions\16

Enjoy
C

How do I - Get a list of email addresses of all users who belong to a given SharePoint security group?


One of mentees asked me how to extract the emails of all the owners of a given site.  With some Powershell magic and the use of the -ExpandProperty switch, this is actually quite easy.

$url = "http://{webapp}/sites/{site}";
$owners = Get-SPWeb $url | Select -ExpandProperty SiteGroups | Where {$_.Name.ToLower() -match ((Get-SPWeb $url).Title.ToLower() + " owners")} | Select -ExpandProperty Users | Select Email;
foreach ($owner in $owners)
{
   {do something with $owner.Email}
}

In the above script, we start off by setting the $url variable.  Then we make the big call to get the owners list of emails into $owners.
The command calls Get-SPWeb for the given URL.  The return of that is then piped to a Select command using the -ExpandProperty switch to expand the SiteGroups.  This would get you all site groups, but piping it to the Where command allows us to filter the returned groups down to the one we want.
The Where command is fed the name of the site group (in lower case) and is asked to match it against the spweb object's title (in lower case) with " owners" concatenated.  A couple of things to note:

  1. It is good practice to ALWAYS make the case of text the same on both sides of a text based comparison.  I prefer lower case, hence the dispersion of .ToLower() in my code.
  2. This part of the code does depend on default security groups being used in SharePoint.  This is 95% of the time the case, but you should be aware that different naming conventions may have been used for the name of the site owners group.
Now that we have the owners security group, the result is piped to another Select command expanding to return the users of that group.  Finally, we pipe to a Select statement that selects a single field i.e. the email address.

You now have an array of email addresses that can be iterated and used via the .Email field reference.

Enjoy
C

How to load a dot net class in Powershell when you don't know the DLL name.


Most of us know that the main SharePoint classes live in Microsoft.SharePoint.dll.  The easiest way to load this assembly is by using the ::LoadWithPartialName method thus releasing you from having to use the exact DLL name.  In this case its obvious, but there are a couple of classes that are in DLLs that are not so obvious.  Simply call the following:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint");

You can replace the "Microsoft.SharePoint" with any class name, provided its accessible on the system.

Enjoy
C

How do I - Allow someone the ability to TRUNCATE TABLE in SQL Server without granting them DBO rights?


There comes a time where a developer produces a stored procedure (sproc) that tries to clear a table quickly. In the case of permanent tables, this is usually done using the TRUNCATE TABLE statement in TSQL. Of course in a production setting, you will have limited rights on your SQL Server and databases. As such, we generally grant users DB_DATAREADER and DB_DATAWRITER rights. Additionally, we'll grant users EXECUTE rights on sprocs. The problem is that in order to do a TRUNCATE TABLE, SQL actually drops that table in order to clear it so quickly. That won't be possible when a users is trying to execute the sproc in production. Since they have EXECUTE rights, they'd be able to execute it, but it will fail when the system tries to drop the table in question. For that, the executor needs to have DBO rights. Of course we're not going to grant people DBO rights in production! So the question becomes, how do you solve the problem?

I'm assuming that well defined change management and process control is in place already. If it is not, you need to start there first. Once you have a proper method for reviewing changes to sprocs going into production, you can apply this quick fix:

TSQL BEFORE

ALTER PROCEDURE [dbo].[BlaBlaBla]
@TableName varchar(60)
AS    
DECLARE @QUERY NVARCHAR(200);
...

TSQL AFTER

ALTER PROCEDURE [dbo].[BlaBlaBla]
@TableName varchar(60)
WITH EXECUTE AS OWNER
AS
DECLARE @QUERY NVARCHAR(200);
...

By simply adding the WITH EXECUTE AS OWNER line to the sproc, it will execute as owner level permissions, thus allowing the TRUNCATE statement to succeed.  Now it's important to understand that this creates a security risk that YOU must manage.  That's why it's important to review any and all changes that are made to the sprocs, especially if they're going to have this statement in them.  It may be better to just create a generic sproc that you can feed a table name to, which elevates like this and truncates the table passed to it.

Enjoy
C

How do I - Determine what type of site template was used when a SharePoint site was created?


If you work in SharePoint regularly, you may develop the ability to look at a SharePoint site and correctly guess which template was used in the creation of said site.  Of course, given how SharePoint works, you'd probably only have a 50/50 shot at best of actually being correct.  Given how most everything is connected to Features and the fact that site templates in most cases just have a given sub set of Features that are turned on during the creation process, it's really anybody's guess.
Powershell to the rescue!
Of course, if you're an administrator with shell access to your farm, you can always resort to Powershell to get that answer.  All you have to do is get the WebTemplate property of the SPWeb object for the site thus:

Write-Host (Get-SPWeb http://sharepoint.crayveon.com/sites/site123 | Select WebTemplate);

Alternatively, you could use the Get-SPSite cmdlet at the beginning of the pipe to get all the sites and sub sites and their templates thus:

Write-Host (Get-SPSite http://sharepoint.crayveon.com/sites/site123 | Get-SPWeb | Select URL, WebTemplate);

Enjoy
C

My "Save site as template" option is gone from Site Settings in SharePoint 2013... where did it go?


A common practice in the world of SharePoint is to create sub site templates.  A site collection administrator will customize a template sub site and then via the Site Settings use the "Save site as template" option to create a new template that can be used for new sub sites.  The problem is that this option isn't always available.  So why not?
The answer lies in the activation of Publishing Features on the site collection.  If at any point in time during the entire life cycle of the site, you had turned Publishing on for the site collection, this option is removed permanently.
See https://support.microsoft.com/en-us/kb/2492356 for more detail.

Later
C

How to convert text to upper or lower case in InfoPath


If you're developing SharePoint forms with extended functionality, then you've most likely already been exposed to InfoPath.  You know, that thing that Microsoft said was dead years ago? :-)
Unfortunately, InfoPath doesn't have the maturity of something like Visual Studio and C# when it comes to text handling.  It would have been great if we could have had access to a .ToUpper() or .ToLower() method when dealing with text, but alas, we do not.
To that end, we have to use a workaround leveraging the translate() method.  In order to generate the equivalent of .ToUpper() which converts all text to uppercase, you'll need to use this:

translate(., "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

Vice versa, to get a .ToLower() equivalent which converts all the text to lowercase, you'll need:

translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")

Enjoy
C

How do I - Fix the problem where SharePoint Designer 2013 crashes upon opening a site?


This one is REALLY annoying!

I never understood why software vendors always assume that users are on the latest and greatest version of their software.  Furthermore, they assume you're only using the current version by itself.  The REAL WORLD REALITY is much further removed from the utopian vision most vendors have of their products.

In my case, this was a problem back in the previous version cycle, when I upgraded from SharePoint 2007 to 2010.  My clients were migrating from 2007 to 2010 thus having content in both formats.
Of course, this problem didn't go away or get fixed, but instead it persists to this day with the version upgrade from SharePoint 2010 to 2013 and I'll be you dollars to donuts we'll see this problem going from 2013 to 2016 as well.

Synopsis

A large company with multiple terabytes of content decides to upgrade from SharePoint 2010 to 2013.  Given the large amount of data, it's a phased migration over time.  As a result, some sites are in 2010 and some are in 2013.  End user support staff will need to be able to support both 2010 and 2013 sites.  When these users install SharePoint Designer 2013 and then try and open a SharePoint 2013 site with SPD 2013, without uninstalling SPD 2010 i.e. running 2010 and 2013 parallel, then SPD could crash consistently.  Oh, and forget about going back to 2010 also.  It will also now be crashing upon opening a site.  I consider this to be a product bug.
I really wish concurrent version testing would have higher priority with software vendors.

Solution

To solve this little conundrum, we're going to have to hack the system... time to fire up good old REGEDIT.  Follow these steps to clear up the problem.

  1. Click the Windows start button.
  2. Type "regedit" and press enter.
  3. In the registry editor, locate the following key:
    HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Common\Open Find\Microsoft SharePoint Designer\Settings\Open Site
  4. Delete the "ClientGUID" key located here.
  5. Locate the next key:
    HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Common\Open Find\Microsoft SharePoint Designer\Settings\Open Site
  6. Delete the "ClientGUID" key here.

Now restart SharePoint Designer and your sites should open as expected.

NOTE:  Your most recently used list will now show both 2010 and 2013 sites.  It's recommended to keep using SPD 2010 with SharePoint 2010 sites and 2013 with 2013 sites.

Hopefully this post saves someone some time and frustration.

Later
C

SharePoint Audit Log reporting


SharePoint has some built in Audit Log reporting that can be leveraged for rudimentary auditing purposes.  In order to these reports to become available, you will need to configure the Audit Settings on a per site collection basis from the /_layouts/15/settings.aspx page.
  1. From your site collection root, click the Settings gear in the top left of the page.
  2. On the dropdown menu, click "Site Settings".
  3. Under the "Site Collection Administration" section, click the "Site collection audit settings" (/_layouts/15/AuditSettings.aspx) link.
  4. On the "Configure Audit Settings" page, you can configure a multitude of options such as audit log trimming and the different events to audit.
  5. Once you've configured your desired settings, click the "OK" button to close the page and enforce the settings.
You will have to wait for the audit processing timer jobs to run before reports will become available.  The delay time will depend on your system's configuration, but by default these jobs run daily.
To view the reports, simply return to the site settings page and click "Audit log reports" (/_layouts/15/Reporting.aspx?Category=Auditing) under the Site Collection Administration section.
From this page, you will have several report options.



Play around with these reports and see what's useful to you.
Happy SharePointing!

Later
C

How do I - Fix SharePoint ULS Logs not being written?


From time to time, you will run into a situation where you're looking for ULS logs in their regular place i.e. %CommonProgramFiles%\Microsoft Shared\Web Server Extensions\15\LOGS\ but there aren't any log files being written.  Most of the time, the problem is simply that an admin might have changed the logging location.
  1. Ensure you're checking the correct logging location.
  2. Navigate to Central Admin >>> Monitoring >>> Configure Diagnostic Logging
  3. Check the Path value.  You can copy the value and paste it into your Windows Explorer address bar for quick access to the logging location.
If the logging location is correct, but you're still not getting logs on the server, check the Trace service.
  1. Open the Service console from the server where the logs aren't being written.
  2. Scroll down and check that the "SharePoint Tracing Service" is running.
  3. If the service is running, you may want to restart it for good measure.
  4. Most likely it's NOT running though.  If it's not, start the service.
  5. If the service does not use Local System for the account, but instead uses a domain account, it's likely that the password might have changed recently.  Obtain the new password and reset the credentials for the service and then restart the service.
If that isn't the issue, there's sometimes some access issue with the logging location at the time the service started up.  You could try toggling the logging location path value.
  1. Navigate to Central Admin >>> Monitoring >>> Configure Diagnostic Logging
  2. Check the Path value.  Change it to a folder that does exist.
  3. Click OK.
  4. Navigate back to the "Configure Diagnostic Logging" page.
  5. Change the Path value back to %CommonProgramFiles%\Microsoft Shared\Web Server Extensions\15\LOGS\ and click OK.
I'll continually add more steps as I uncover more reasons logging doesn't work in SharePoint. ;-)

Later
C

How do I - Manually force SharePoint patches or CU installations to complete using PSConfig.exe


SharePoint patching has always been a sore subject.  The installation of Cumulative Updates can leave your environment in a non functional state which is why Microsoft always recommend that you only install CUs if you are absolutely sure that the CU actually fixes something that is broken within your environment.  As with all other installation recommendations, it's always recommended that patches and CUs follow a proper change management path of DEV >>> QA >>> PROD in order to ensure minimal impact to production systems.

My standing recommendation to all my clients is as follows:


  1. Do NOT install any CU until it's been in the wild for at least a month.  There has been many retractions and re-publishings of CUs over the course of history and unfortunately, Microsoft's track records with CU regressions is not stellar.  The last thing you need to to install a CU that breaks your production environment.  By waiting a month, any obvious issues with the CU would become known before you apply it to your environment.
  2. Install the target patch or CU into DEV first.  Let it simmer in this environment for 2 weeks.  Ensure that you have the ability to roll back the CU through VM snapshots.  Since most DEV environments are virtual these days, this isn't really a problem.  If ANY issue is reported by developers in the DEV environment, investigate it and do NOT proceed to step 3 until you're sure the problem was not caused by the CU.  If the CU is the cause of the problem, simply roll back to your previous snapshot. Be sure this policy is well published and communicated, especially within your developer community as developers would lose any code that resided on the DEV farm without being checked into source control, when you conduct the rollback.  But that never happens, right?  Developers always check their code into source control, right? ;-)
  3. Once the CU passed the DEV test, it's time to move it to your QA environment for validation against what should be in PROD.  Again, the assumption here is that your QA environment is a true representation of the PROD environment.  If the CU breaks anything in QA, simply stop the process and do NOT proceed to step 4.  As with step 2 above, if your QA environment is also virtual (most are these days) then you can simply roll it back as well when problems are encountered.  Leave the CU in the QA environment for at least 2 weeks to simmer before advancing to step 4.
  4. Finally deploy the CU to your PROD environment.  Since there is no uninstall for CUs, and given that a lot of PROD SQL Servers are still physical, once you get to this point, there's no going back.  If some regression issues are discovered after you've installed to PROD, you're at the mercy of Microsoft releasing a patch to resolve the issue.  It is best to open a support ticket as soon as possible to help them determine how widespread and serious the regression is. 
Now in each of the install steps above, you'll sometimes find that running PSConfig.exe or the Configuration Wizard after completing the CU/Patch installation, would fail or get stuck.  This leaves you in no man's land and has to be resolved in order to complete the installation/upgrade.

NOTE:  A CU/PATCH INSTALLATION IS NOT COMPLETE UNLESS YOU HAVE A SUCCESSFUL RUN OF PSCONFIG ALL ALL YOUR SERVERS IN TURN!!!

When this happens, you can manually run PSConfig to force the completion as follows:

  1. Run the SharePoint Management Shell as admin.  (Right click icon, Run as Administrator)
  2. There should be a path to BIN already defined in the management shell, but if you get a command not found error when you enter PSConfig, you could manually change directory to C:\Program Files\Common Files\Microsoft Shared\web server extensions\14\BIN
  3. Execute psconfig.exe -cmd upgrade -inplace b2b -force -cmd applicationcontent -install -cmd installfeatures

If PSConfig fails on step 9 or 10, proceed as follows:

  1. Edit C:\ProgramData\Microsoft\SharePoint\Config\GUID\cache.ini
  2. Reset value to 1
  3. From the SharePoint Management Shell, execute stsadm -o execadmsvcjobs
  4. From the SharePoint Management Shell, execute psconfig.exe -cmd upgrade -inplace b2b -wait -force
This should complete your update successfully.

REMEMBER THAT YOU HAVE TO DO THE ABOVE PSCONFIG FOR EACH AND EVERY SERVER IN YOUR SHAREPOINT FARM.  START WITH THE CENTRAL ADMIN SERVER AND THEN PROCEED WITH EACH SERVER IN TURN ONCE THE PREVIOUS SERVER COMPLETED.

It is said that you don't HAVE to run PSConfig in a single threaded fashion as stated above, but from personal experience, whenever I've had issues with CU installations, it was because PSConfig was run concurrently on multiple servers in the farm, but I'd recommend you let your own personal experience be the judge.

Enjoy
C




How do I - Update the SharePoint Search Service Application Default Content Access account credentials using Powershell


When corporate security policy require that account passwords be updated, the Default Content Access Account used for the Search Service Application needs to be manually updated as well.  If your Search Service is in a funky state, this might not be possible using the browser UI.  When this happens, you'll need to resort to Powershell to save the day using the following script:

$userid = "domain\search_account";
$password = "account_password";
$ssa = Get-SPEnterpriseSearchServiceApplication "Search Service Application";
$sac = New-Object Microsoft.Office.Server.Search.Administration.Content($ssa);
$sac.SetDefaultGatheringAccount($userid, (ConvertTo-SecureString $password -AsPlainText -force));

Enjoy
C

SharePoint PowerShell error - The local farm is not accessible - Cmdlets with FeatureDependencyId are not registered


When you're new to a SharePoint farm, you may try to open the SharePoint Management Shell expecting to work in PowerShell.  Even though your account may be a local administrator on the server as well as a member of the SharePoint Farm Administrators group in Central Admin, you may still receive this error message:

The local farm is not accessible. Cmdlets with FeatureDependencyId are not registered.




The issue is related to access to the configuration database for SharePoint.  What you need to do is grant access to the user in question, to the configuration database in SQL Server.  This can be done with the following two PowerShell commands:


$db = Get-SPDatabase | where {$_.Type -eq "Configuration Database"}
Add-SPShellAdmin "domain\username" -database $db

We begin by using the Get-SPDatabase cmdlet to get a reference to the SharePoint databases.  When you execute Get-SPDatabase by itself, it will provide you a listing of all SharePoint databases in the farm.  By filtering the type of database to only "Configuration Database" via the where clause, we are able to return on the configuration database in the farm.
Using the returned reference from this, we can use the Add-SPShellAdmin cmdlet to grant the user in question access to the configuration database.

Happy SharePointing!
C

image

How do I - Sort a list of custom class objects in descending order in C#?


Suppose we have a custom class defined thus:

    class CustomClass
{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public int Int1 { get; set; }
public int Int2 { get; set; }

public CustomClass()
{
String1 = "";
String2 = "";
String3 = "";
Int1 = 0;
Int2 = 0;
}

public int Calculate()
{
return Int1 + Int2;
}
}


As we can see, the class initializes it's variables and has a Calculate() method that simply returns the added value of the two int variables of the class.  Now suppose we have a list of these class objects in our app with varying values thus:



    CustomClass MyClass = new CustomClass();
MyClass.Int1 = 10;
MyClass.Int2 = 5;
CustomClass My2ndClass = new CustomClass();
My2ndClass.Int1 = 7;
CustomClass My3rdClass = new CustomClass();

List<CustomClass> lst = new List<CustomClass>();
lst.Add(My2ndClass);
lst.Add(MyClass);
lst.Add(My3rdClass);



We now have a list containing 3 of our custom class objects.  The first object in the list has an Int1 value of 10.  The second has an Int1 value of 5 and the third, given the class' initialization method, will have an Int1 value of 0.  Given the order that we added the class objects to our list, the list, when looking at the Int1 field would look thus:



7, 10, 0



How do we go about sorting this list by any one of the variable fields of the custom class, say Int1 in our case?



The built in support for Linq in Visual C# and the .NET Framework, actually makes this very easy.  Using a Lambda expression, the task becomes a one liner thus:



    lst = lst.OrderByDescending(x => x.Int1).ToList();



We start with the OrderByDescending() method and then pass the Lambda expression to reference back to the object itself.  The x => part of the expression tells C# to reference back to the calling object which in our case is lst.  C# will reference back to lst as x which is where the x.Int1 then instructs the OrderByDescending method to use the Int1 field as the value by which to sort, in descending order, the x object which translates to lst. 



By appending the ToList() method to the back of the statement, C# will take the sorted result and generate a new List<> object which we simply assign back to the original lst variable. If we needed to sort in ascending order we would use the OrderBy() method instead thus making the statement:



    lst = lst.OrderBy(x => x.Int1).ToList();




Enjoy

C



image

0 comments

Posted in

I am humbled to be honored again


I am humbled to be honored as a Microsoft MVP for SharePoint Server, for the 9th time.  The MVP community is a great group of people that I'm proud to be associated with.

How do I – Play the TNG Red Alert (or any audio file for that matter) in my WinForms C# app


Out of the box, C# and .NET supports the playing of any .WAV file.  The code is pretty straight forward.  We simply need to reference the system media class to create a SoundPlayer{} object thus:

System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"C:\Media\MySoundEffect.wav");





Once we have the object, we simply invoke the Play() method thus:



p.Play();





So in two simple lines of code, we are able to play any .wav file from our application.  There are some more embedded problems that may not immediately be obvious.  Allow me to explain.



Suppose I'm trying to add the Star Trek TNG Red Alert sound to my app for an emergency notification.  I begin by locating the sound in question.  A quick web search takes me to the http://www.trekcore.com/audio/ site where the Red Alert section has several samples to choose from.  Once I locate the sound sample that I wish to use, I click the link and download the file.



image



PROBLEM #1:  The file is in the .mp3 audio format.  Attempting to use it in the instantiation of the SoundPlayer() method fails with the message that only .wav files are supported.



RESOLUTION

We could use a custom media player class, or we could just let the internet do some work for us.  A quick search pointed me to the http://media.io/ online audio conversion web site.  The simple interface couldn't be cleaner or more straight forward:



image



Simply take the .mp3 file we previously downloaded and upload and convert it on this site.  Now we finally have a .wav file that we can use in our project.  Now we would like the alert sound to be played 3 times and a popup window present the error notification.  That's easy, right?  We simply use a for() loop that looks like this:



for (int i = 0; i <= 2; i++)
{
System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"TNG-Red-Alert.wav");
p.Play();


}


MessageBox.Show("There is an error!", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); 





We compile and run the app and expect the alert sound to play 3 times… only… it doesn't.  So why doesn't it play our audio?  It seems the problem is related to the fact that the asynchronous .Play() method, being spun from the current executing thread, does not have enough time to execute before the modal .Show() method is executed.  As you know, the MessageBox.Show() method will block the existing thread from executing any further until user action is taken on the window.  As such, the Play() method is queued really quickly given present computer CPU speeds, and once the.Show() method is invoked, it aborts the asynchronous threads to the .Play() method.  To solve the problem we have to allow the .Play() method some time to begin execution.  The easiest way to do that is to simply .Sleep() the current thread.  By injecting this line of code:



System.Threading.Thread.Sleep(1500);





right after the .Play() call, the currently executing thread will pause for 1.5 seconds.  Depending on what audio you're playing, you can tweak this value (in milliseconds) to match accordingly.



At this point, the alert message and audio work correctly… but the OCD in me still isn't happy with the effect we get.  When the error condition is generated, the audio alert is played three times, per our code, and then the error alert is popped up.  The problem I have with that is that the audio warning gets my attention, but then I have to wait for it to finish playing three times before the error message with useful information pops up.  That should be easy to fix, right?  Why not just simply move the .Show() method call to before the for() loop containing the .Play() call?



Unfortunately, this doesn't work as you'd expect.  Because of the fact that the .Show() call actually freezes the current thread, the message box is displayed and then awaits user interaction before it allows the audio code to execute.  What we need is for the .Show() code to be spun off in another thread so as to allow the audio alert to play while the message is displayed.  To do this, we create a new method thus:



public void ShowAlert()
{
System.Threading.Thread t = new System.Threading.Thread(() =>
{
MessageBox.Show("There is an error!", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
t.Start();
}





Once we have the message box contained in a separate thread, we simply make that call prior to playing our audio thus:



ShowAlert();
for (int i = 0; i <= 2; i++)
{
System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"TNG-Red-Alert.wav");
p.Play();
System.Threading.Thread.Sleep(1500);
}





Once the user interacts with the message box, the thread (t) will terminate gracefully.  In the mean time our main app is free to continue execution of our loop to play the alert sound.





Enjoy


C



image

How do I – Play the TNG Red Alert (or any audio file for that matter) in my WinForms C# app


Out of the box, C# and .NET supports the playing of any .WAV file.  The code is pretty straight forward.  We simply need to reference the system media class to create a SoundPlayer{} object thus:

System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"C:\Media\MySoundEffect.wav");





Once we have the object, we simply invoke the Play() method thus:



p.Play();





So in two simple lines of code, we are able to play any .wav file from our application.  There are some more embedded problems that may not immediately be obvious.  Allow me to explain.



Suppose I'm trying to add the Star Trek TNG Red Alert sound to my app for an emergency notification.  I begin by locating the sound in question.  A quick web search takes me to the http://www.trekcore.com/audio/ site where the Red Alert section has several samples to choose from.  Once I locate the sound sample that I wish to use, I click the link and download the file.



image



PROBLEM #1:  The file is in the .mp3 audio format.  Attempting to use it in the instantiation of the SoundPlayer() method fails with the message that only .wav files are supported.



RESOLUTION

We could use a custom media player class, or we could just let the internet do some work for us.  A quick search pointed me to the http://media.io/ online audio conversion web site.  The simple interface couldn't be cleaner or more straight forward:



image



Simply take the .mp3 file we previously downloaded and upload and convert it on this site.  Now we finally have a .wav file that we can use in our project.  Now we would like the alert sound to be played 3 times and a popup window present the error notification.  That's easy, right?  We simply use a for() loop that looks like this:



for (int i = 0; i <= 2; i++)
{
System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"TNG-Red-Alert.wav");
p.Play();


}


MessageBox.Show("There is an error!", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); 





We compile and run the app and expect the alert sound to play 3 times… only… it doesn't.  So why doesn't it play our audio?  It seems the problem is related to the fact that the asynchronous .Play() method, being spun from the current executing thread, does not have enough time to execute before the modal .Show() method is executed.  As you know, the MessageBox.Show() method will block the existing thread from executing any further until user action is taken on the window.  As such, the Play() method is queued really quickly given present computer CPU speeds, and once the.Show() method is invoked, it aborts the asynchronous threads to the .Play() method.  To solve the problem we have to allow the .Play() method some time to begin execution.  The easiest way to do that is to simply .Sleep() the current thread.  By injecting this line of code:



System.Threading.Thread.Sleep(1500);





right after the .Play() call, the currently executing thread will pause for 1.5 seconds.  Depending on what audio you're playing, you can tweak this value (in milliseconds) to match accordingly.



At this point, the alert message and audio work correctly… but the OCD in me still isn't happy with the effect we get.  When the error condition is generated, the audio alert is played three times, per our code, and then the error alert is popped up.  The problem I have with that is that the audio warning gets my attention, but then I have to wait for it to finish playing three times before the error message with useful information pops up.  That should be easy to fix, right?  Why not just simply move the .Show() method call to before the for() loop containing the .Play() call?



Unfortunately, this doesn't work as you'd expect.  Because of the fact that the .Show() call actually freezes the current thread, the message box is displayed and then awaits user interaction before it allows the audio code to execute.  What we need is for the .Show() code to be spun off in another thread so as to allow the audio alert to play while the message is displayed.  To do this, we create a new method thus:



public void ShowAlert()
{
System.Threading.Thread t = new System.Threading.Thread(() =>
{
MessageBox.Show("There is an error!", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
t.Start();
}





Once we have the message box contained in a separate thread, we simply make that call prior to playing our audio thus:



ShowAlert();
for (int i = 0; i <= 2; i++)
{
System.Media.SoundPlayer p = new System.Media.SoundPlayer(@"TNG-Red-Alert.wav");
p.Play();
System.Threading.Thread.Sleep(1500);
}





Once the user interacts with the message box, the thread (t) will terminate gracefully.  In the mean time our main app is free to continue execution of our loop to play the alert sound.





Enjoy


C



image

0 comments

Posted in

For all the audio aficionados


A very interesting Kickstarter campaign for a portable DAC.

http://www.kickstarter.com/projects/gavn8r/geek-a-new-usb-awesomifier-for-headphones/description

0 comments

Posted in

Fixing Visual Studio 2012 defaults


One of the biggest pet peeves I experienced when switching to Visual Studio 2012 was the fact that the default command buttons were changed.  In going for a cleaner look, many of the main command buttons most developers use most frequently, was removed from the toolbar interface and buried in the menu structure instead.
Personally, the buttons I find myself using most frequently are the Navigate Backwards and Navigate Forwards (image ), Comment Selected Lines and Uncomment Selected Lines (image ) and lastly Increase Line Indent and Decrease Line Indent (image ).
The Navigate functions are still there, but the Comment and Indent functions are buried deep within the Edit/Advanced menu.  While it’s true that the Comment functions do have hot keys attached to them, I hardly consider Ctrl+K,Ctrl+C for a single click to be useful to most developers save the few that know the hotkeys by heart.
As for the Indent commands, they don’t have any hotkeys and you’d have to click through Edit/Advanced/Indent to get the action.  Though small annoyances, these can be addressed by simply adding the commands back to the command bar.  Here’s how to do just that:
  1. Click Tools on the menu bar.
  2. image
  3. On the popup menu, click "Customize".
  4. After the Customize dialog window opens, click over to the "Commands" tab.
  5. Select the "Toolbar" radio button.
  6. In the dropdown to the right of the Toolbar radio button, select the "Standard" toolbar to work with.
  7. The toolbar's controls will be previewed in the bottom left of the dialog window.
  8. To the right of the preview, click the "Add Command" button.
  9. image
  10. In the Add Command dialog window that opens up, select the "Edit" category to the left.
  11. On the right, scroll down through the commands and locate the "Line Indent" command.
  12. Select the command and click the "OK" button.
  13. image
  14. The command will be added to the top of the Controls preview.
  15. image
  16. Repeat steps 8 through 12 for the "Line Unindent", "Selection Comment" and "Selection Uncomment" commands as well.  The interface doesn't allow multi-select, though that would be nice touch in a future update.
  17. Once you have all the commands, use the "Move Up" and "Move Down" buttons to the right to arrange the commands in the order you wish them to appear on the toolbar.
  18. image
  19. When you're happy with the arrangement, click the "Close" button.  Your new toolbar should now reflect your favorite buttons again. :-)
  20. image

Happy coding!
C
image