Neudesic Blogs

Passion for Innovation
BISM Tabular Security

I have been focused on the security options within SSAS Tabular models lately and I have found a few ways to approach it. There is straight forward AD roles tied to a specific link to the data, but this is not very dynamic in nature and all of these roles must be hard coded to a specific row level item such as region or division. In the dynamic variety two are fairly well documented and the third is my own hybrid of the two solutions, which I will go in to detail to explain. Before I get into these I will detail out some of the general concepts.

First and foremost there is roles based security within the tabular models, but don't confuse that with dynamic security using AD roles. There is a bit of a disconnect amongst these two things when trying to create dynamic security roles. When using straight hard coded roles such as and AD group tied to a specific division this works like a charm. Most folks do not want to maintain this type of structure when you grow past just a handful of rows to maintain though. For the most basic types of security go through the lesson 12 in the Tabular Modeling segment of the Adventure Works Tutorial.

 

This leads us to a few more dynamic ways of doing this and the main issue you run into here is that to do so you must tie the row-level data back to a user by using the USERNAME() function within DAX.  This forces your role level permissions to be managed at the user level.  You can still tie specific roles to have membership to the role you are defining by allowing AD Roles in the membership, but when you start tying the data back dynamically it is forced down to the user.

 

First of the dynamic options is the method that MS shows you to use in the MSDN supplemental lesson Implement Dynamic Security by Using Row Filters.  In this lesson it details how you would tie a user in your AD to a single record type in the dimension you are working with.  The first thing I did was figure out a way to tie this to more then one table and also more then one type of record in each table.  So to go into more detail you can tie a user to not only one territory, but many by adding more records into the same table with additional 'Employee Security'[Sales Territory Id]'s, which doesn't even require a different DAX statement as they showed in there. That statement is…

 

='Sales Territory'[Sales Territory Id]=

LOOKUPVALUE('Employee Security'[Sales Territory Id],

   'Employee Security'[Login Id],

   USERNAME(),

   'Employee Security'[Sales Territory Id],

   'Sales Territory'[Sales Territory Id])

 

Essentially this DAX statement is returning a Boolean value of True or False.  So the first item shown is the 'Sales Territory'[Sales Territory Id] that is basically calling our which column in the 'Sales Territory' table this is going to be checking against on a per record basis.  Then there is an equal sign to kick get to the latter part of the logical test, which in this case is looking up what the 'Employee Security'[Sales Territory Id] is that is defined in the 'Employee Security' table.  The last 4 values actually consist of two paired values that do the lookup checks on the 'Employee Security' table and check the value against the values you would like to match it to.  Starting with the first one it looks up the 'Employee Security'[Login Id] to match it with the logged in user within AD using the USERNAME() function.  The second checks to make sure the row within the 'Employee Security' table matches the records within the 'Sales Territory' table.  When using this it is not tied to a single record within the 'Employee Security' table and will allow you to have a single employee listed more then one time with more then one [Sales Territory Id].  Thus allowing different territories and a duplicated user within different records in that table to be referenced with this same statement, cause often times I can see the need for more then one territory to be allowed for various users that can be at the regional management level where they may manage multiple territories.

 

I also thought that this can become even more complicated when you also tie it to some other table as well, especially to do data management on this table.  So by adding another column to the 'Employee Security' table you can also add in security on the 'Product Category' table as well.  So I added another column with the name [Product Category Id] that will tie back to the column 'Product Category'[Product Category Id].  This will be used in a second DAX statement within the same role, although this will sit the Row Filter for 'Product Category' table.

 

='Product Category'[Product Category Id]=

LOOKUPVALUE('Employee Security'[Product Category Id],

   'Employee Security'[Login Id],

   USERNAME(),

   'Employee Security'[Product Category Id],

   'Product Category'[Product Category Id])

 

When I looked at all of this and also thought about managing the data within the 'Employee Security' I started to think there must be a better way to handle this.  So I used a technique that will allow you to make the table more generic and then use key name and key pairs tied to users' [Login Id] field.  Instead of the table that was recommended within the lesson on MSDN above, you can user the following structure instead.

  

CREATE TABLE [dbo].[DimSecurity](

[LoginID] [nchar](50) NULL,

[KeyName] [nchar](20) NULL,

[Key] [tinyint] NULL

) ON [PRIMARY]

 

GO

 

Fill in the table with values that tie to the security you created above.  This would mean you fill in the first column with the 'Employee Security'[Login Id] and the third would use either 'Employee Security'[Sales Territory Id] or  'Employee Security'[Product Category Id]. The new column in the middle is the trick here that allows you to use this generic table approach and this would require a text field based on the name of the key you are tying it back to.  In the cases here it would be "Sales Territory Id" and "Product Category Id" respectively.

 

LoginId

KeyName

Key

domain\user1

Sales Territory Id

1

domain\user1

Sales Territory Id

2

domain\user1

Product Category Id

1

domain\user1

Product Category Id

2

domain\user1

Product Category Id

3

domain\user1

Product Category Id

4

domain\user2

Sales Territory Id

1

domain\user2

Sales Territory Id

2

domain\user2

Sales Territory Id

3

domain\user2

Sales Territory Id

4

domain\user2

Sales Territory Id

5

domain\user2

Product Category Id

1

 

 

You would use the trio of values to tie back the different Row Filters by creating the following statements within the new Role.  First let's start off with the Sales Territory Row Filter by adding the following DAX statement to that filter first.  The difference here is that you now also use the 'DimSecurity'[KeyName] paired with "Sales Territory Id" in the LOOKUPVALUE function to pair the proper valued pairs from the 'DimSecurity' table first. 

 

='Sales Territory'[Sales Territory Id]=

LOOKUPVALUE('DimSecurity'[Key],

            'DimSecurity'[LoginID],

            USERNAME(),

            'DimSecurity'[KeyName],

            "Sales Territory Id",

            'DimSecurity'[Key],

            'Sales Territory'[Sales Territory Id])

 

You will also need to do the same on the Product Category Row Filter as well to get the same results as we got in the previous example.

 

='Product Category'[Product Category Id]=

LOOKUPVALUE('DimSecurity'[Key],

            'DimSecurity'[LoginID],

            USERNAME(),

            'DimSecurity'[KeyName],

            "Product Category Id",

            'DimSecurity'[Key],

            'Product Category'[Product Category Id])

 

Now that you have both in you can go and test it out using the "Analyze in Excel" option.  I think the generic table is a bit more flexible then the option provided in the MSDN post mentioned, mostly due to the ability to manage the data in the security table much easier then if you had both [Sales Territory Id] and [Product Category Id] columns in that same dataset all tied to one user, because in this model you would still have to also add multiple records for each user to tie out every combination of the allowed rows in that table.

 

So this also leads me to a post by Teo Lachev that details out how you can use a factless fact bridge table to do something very similar to the approach I have come up with.  Not wanting to steal anything away from Teo go ahead and read that post here.

 

I don't feel any option here is perfect and will fix all your needs, but one of these options listed should get you to where you need to be. 

Posted: May 10 2012, 09:09 by Tom.Marek | Comments (1) RSS comment feed

Tags: , , , , ,
Categories: Business Intelligence

Slickgrid Currency Column Formatter

 

Slickgrid is a JavaScript grid component by Michael Leibman.  This is a great grid which is very flexible and customizable and very fast to load and display data.  Over the next few weeks we will be blogging about Slickgrid in general as well as a few areas in which we have extended its functionality.  This blog will address one of those areas where functionality was extended.

 

I recently came across the need to display pricing information for products and could not find information on a currency column formatter.  The raw data returned from my web service displayed the data as 0.0000.  So there was no currency formatting ($ symbol) and the decimal precision was returned to four places.  I wanted the data to be formatted like typical US currency with a $ and to two decimal places, like this: $0.00.  Maybe I didn't look hard enough to find a ready-made solution, but at any rate, I quickly wrote my own currency formatter and thought it might help someone else looking to do something similar.

 

The column formatting functions for Slickgrid are contained in the slick.formatters.js file.  I appended this file with a new function by adding the following bit of code:

 

function CurrencyFormatter(row, cell, value, columnDef, dataContext) {

    if (value === null || value === "" || !(value > 0)) {

        return "$" + Number();

    } else {

        return "$" + Number(value).toFixed(2);

    }

}

 

Additionally, you will need to modify the top section of the script to register the newly created namespace.  I added the following code (highlighted in yellow) to the existing function:

 

(function ($) {

  // register namespace

  $.extend(true, window, {

    "Slick": {

      "Formatters": {

        "PercentComplete": PercentCompleteFormatter,

        "PercentCompleteBar": PercentCompleteBarFormatter,

        "YesNo": YesNoFormatter,

        "Checkmark": CheckmarkFormatter,

        "Currency": CurrencyFormatter

      }

    }

  });

 

Now when you define your columns in your html page that will display your Slickgrid control, you need to add the following bit of code to the column definition to reference the currency formatting function that we just created above:

 

                columns = [

                { id: "Price", name: "Price", field: "Price", width: 80, cssClass: "cell-title", formatter: Slick.Formatters.Currency }

            ];

 

That's it.  Now the data shown in the currency formatted column will display as "$0.00", rather than the raw, unformatted data that may be returned in your web service or database call.  If the value being evaluated is null, an empty string or an empty object, the data will be rendered as $0.  This did the trick for me!  Stay tuned for more Slickgrid information in future blogs.  Happy coding.

Announcing the release of Neuron ESB 2.6!

I'm very happy to announce the release of Neuron ESB 2.6. If you are using an earlier release of Neuron ESB, you can download this latest release from here: Neuron ESB 2.6. This release significantly extends the Neuron ESB platform by introducing new capabilities that will allow businesses to more easily scale, develop, connect and operationally manage their solutions. Businesses turn toward Neuron ESB to solve their service and integration problems while reducing the total cost of ownership of their solutions.  Neuron ESB accomplishes this by continually refining and extending its core capabilities in ways that can be effectively leveraged in agile environments by Microsoft .NET developers.

Neuron ESB 2.6 introduces many new features and enhancements, some of which include:

* Multi Instance Runtime
This capability allows organizations to install and run
multiple instances of the Neuron ESB Runtime Service
on a single server. Each instance can be configured as
either a 32 or 64 bit process, capable of running side by
side. This allows organizations to easily partition
business solutions to run on a single server and support
multiple developers.

* Process Designer
Neuron ESB 2.6 now offers a flexible, easy to use,
Process Designer with a drag and drop interface that
ships with over 30 configurable Process Steps that do
everything from calling a service, updating a database or
queue to parsing an Excel file. Organizations can create
more complex business processes using newly
introduced Process Steps such as “While” and “For”
looping constructs as well as an “Excel to Xml” parsing
Process Step.

* Adapters and Connectivity
Several new adapters are introduced as well as
enhancements to assist organizations connect,
compose and expose new capabilities within their
environment. Also in this release is the addition of a fifth
transport which can be used to configure Topics. Some
of these adapters and enhancements are:

- Event Based SharePoint 2010 Publication
- Event Based Dynamics CRM 2011 Publication
- FTP/FTPS Adapter
- ODBC Enhancements to support ReturnValue and OutPut type parameters
- Topics configurable with Named Pipes

* Monitoring and Reporting
New administrative, monitoring and reporting capabilities
are introduced with the release of Neuron ESB 2.6.
However, one of the most sweeping changes affecting
custom reporting is the refactoring of the Neuron ESB
database structure. The Neuron Audit tables have been
refactored specifically enable custom reporting. For
example, custom properties that are added to the ESB
Message are stored in an XML data typed column
supporting XQuery. Some of the features are:

- WMI Performance Counters for Topics and Endpoints
- WMI Performance Counter for Request/Reply call Total Time
- WMI Failed Message Events
- Database Enhancements for Custom Reporting
- Filter Query User Interface for Reporting

* Deployment Management
Neuron ESB supports XCopy Deployment through the
use of its Environmental Variable feature set. Neuron ESB
2.6 augments this through the extension of Environmental
Variable support for the Neuron Audit Database. Some of
the features included to assist deployments are:

- Assignment of the Same Neuron Audit Database to more than one Deployment Group
- Environmental Variable support for Neuron Audit database
- Environmental Variable support for Service Endpoint ACL property
- Read Only ESB Solution Configuration (*.ESB) support within the Neuron ESB Explorer and Runtime


* Service Endpoints
Neuron ESB 2.6 plays a critical role as organizations
evolve to look toward Cloud offerings for service hosting
or business to business communication. As a web
service broker, Neuron ESB 2.6 now includes all of the
Microsoft Azure Service Bus Relay Bindings to facilitate
hybrid approaches to bridge On Premise integration with
Cloud based solutions. Neuron ESB 2.6 ensures that
communication between the Cloud and On Premise
systems can be handled securely and reliably, while
managing the integration requirements of the
organization. Other important features in this release are:

- Azure Service Bus Integration
- Delegation Support for REST based services
- Pluggable WCF Service Behaviors

 

The full details of "What's New in Neuron ESB 2.6" can be downloaded from our web site here: http://products.neudesic.com/media/pdf/whatsnew.pdf

This release includes all the accumulated fixes. The complete list of changes can be found in the Neuron Change Log located at the root of the Neuron ESB installation directory as well as on our web site.


Posted: Apr 02 2012, 05:19 by marty.wasznicky | Comments (6) RSS comment feed

Tags:
Categories: Azure | Connected Systems | Custom Application Development | Headlines | Neuron | Neuron ESB | WCF

C# Code to look up Current User in Active Directory

Here's some re-usable C# code to lookup the currently logged-in user in Active Directory to get various AD properties such as FirstName, Last Name, and Email. The method IsExistInAD() below is handy in intranet applications where your ASCX or ASPX can assume the current user is authenticated in the domain and you need properties of the user from Active Directory. Method IsExistInAD() takes, as input the user name in the format DOMAIN\\alias and performs a Directory search using .NET Directory Services using System.DirectoryServices.ActiveDirectory. If successful, it populates the private SearchResult _result variable with the various properties from Active Directory and returns true. If the Directory search does not find the current user, the IsExistInAD() method returns false. Note this code handles multiple domains, so if some of your users have username e.g. NORTHAMERICA\\bobsmith and other users have e.g. SOUTHAMERICA\\juanparamo, this code handles it by parsing the domain name and using it in the rootDirectory of the Directory Searcher, so it will find the user in the correct ActiveDirectory Domain.

Note that your ASP.NET application gets this user name to pass as input to IsExistInAD() automatically for the currently logged in user from the Page.User.Identity.Name property when your web application is configured for Windows Authentication.

The first time you setup the target server that will run the Site Info Web Application, you must configure IIS to use Windows Authentication. The Site Info Web Application depends on this and it is not the default configuration of IIS. This configuration setting is Windows/IIS and does not require adjustment on future deployments of new builds or upgrades.

How to Configure Windows Authentication


On the target IIS server

From Server Manager, Open Internet Information Services (IIS) Manager


In the left side panel, select the server (e.g. ZLMRCWEB31)


Double-click the Authentication icon to open the Authentication Applet


  1. Enable Windows Authentication

  1. Disable Anonymous Authentication





Code Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

namespace TestADLookupUsersEmail
{
public partial class _Default : System.Web.UI.Page
{
public class ContactADFields
{
public string FirstName;
public readonly string FirstNameProp = "givenname";
public string LastName;
public readonly string LastNameProp = "sn";
public string Email;
public readonly string EmailProp = "mail";
public string FullName;
public readonly string FullNameProp = "displayname";
}

private SearchResult _result;
private ContactADFields contact = new ContactADFields();


protected void Page_Load(object sender, EventArgs e)
{

}
public string getUserIdentityName()
{
return Page.User.Identity.Name;
}
public string getUserEmail()
{
if (IsExistInAD(Page.User.Identity.Name))
{
if (_result.Properties.Contains(contact.FirstNameProp))
{
contact.FirstName = (string)_result.Properties[contact.FirstNameProp][0];
}

if (_result.Properties.Contains(contact.LastNameProp))
{
contact.LastName = (string)_result.Properties[contact.LastNameProp][0];
}

if (_result.Properties.Contains(contact.EmailProp))
{
contact.Email = (string)_result.Properties[contact.EmailProp][0];
}
if (_result.Properties.Contains(contact.FullNameProp))
{
contact.FullName = (string)_result.Properties[contact.FullNameProp][0];
}

int propCount = _result.Properties.PropertyNames.Count;
foreach (string propName in _result.Properties.PropertyNames)
{
try
{
string propVal = (string)_result.Properties[propName][0] as String;
}
catch (Exception)
{
continue;
}

}
}
return contact.Email;
}

/// <summary>
/// Parse a User Identity Name e.g. "REDMOND\\billg" setting the out accountName and out domainName
/// </summary>
/// <param name="path"></param>
/// <param name="accountName"></param>
/// <param name="domainName"></param>
/// <returns>true if successful parsing the input user name</returns>
private bool ParseUserName(string path, out string accountName, out string domainName)
{
bool retVal = false;
accountName = String.Empty;
domainName = String.Empty;

string[] userPath = path.Split(new char[] { '\\' });
if (userPath.Length > 0)
{
retVal = true;
accountName = userPath[userPath.Length - 1];
if (userPath.Length > 1)
{
domainName = userPath[userPath.Length - 2];
}
}

return retVal;
}

/// <summary>
/// Lookup user in AD, and if successful, set SearchResult _result and return true.
/// </summary>
/// <param name="loginName">The Page.User.Identity.Name e.g. "REDMOND\\billg"</param>
/// <returns>True if found in AD. Also sets SearchResult _result.</returns>
private bool IsExistInAD(string loginName)
{
DirectorySearcher search = null;
string userName;
string domainName;
if (ParseUserName(loginName, out userName, out domainName))
{
DirectoryContext dirCtx = new DirectoryContext(DirectoryContextType.Domain, domainName);
if (dirCtx != null)
{
Domain usersDomain = System.DirectoryServices.ActiveDirectory.Domain.GetDomain(dirCtx);
if (usersDomain != null)
{
DirectoryEntry rootDirEntry = usersDomain.GetDirectoryEntry();
if (rootDirEntry != null)
{
search = new DirectorySearcher(rootDirEntry);
search.Filter = String.Format("(SAMAccountName={0})", userName);
}
}
}
}
else
{
search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", loginName);
}

// Adding properties to the DirectorySearcher is supposed to make the
// query more efficient by only returning the fields we want. However,
// doing so seems to always make teh Last Name prop ("sn") return blank.
//search.PropertiesToLoad.Add(contact.FirstNameProp);
//search.PropertiesToLoad.Add(contact.LastNameProp);
//search.PropertiesToLoad.Add(contact.EmailProp);
//search.PropertiesToLoad.Add(contact.FullNameProp);

_result = search.FindOne();

if (_result == null)
{
return false;
}
else
{
return true;
}
}
}
}


Posted: Mar 22 2012, 17:35 by Martin.Cox | Comments (1) RSS comment feed

Tags:
Categories: Custom Application Development

PowerShell commands to help configure a development environment

Continuing my PowerShell fetish for another week I thought I’d write about some of the PowerShell commands I have been using to configure my development environments in this article.

The set of commands I run are used to disable a lot of the security features that are great to have in production but do nothing aside from producing headaches in a development environment.  I’ll start with a base installation of Windows Server 2008 R2 Enterprise and then begin to run these scripts.

First we run this command to make sure that we are allowed to run scripts in the PowerShell command window that aren’t signed.

  Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force

Next up is disabling the IE ESC settings.  We do this by updating the registry keys for this property.

  Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" -Name "IsInstalled" -Value "0"

  Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" -Name "IsInstalled" -Value "0"

We will also want to disable User Access Control as well so we don’t have to keep remembering to run things as administrator.  Again this is just a registry update.

  Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "ConsentPromptBehaviorAdmin" -Value "00000000"

Of course we need to disable the shutdown tracker so we don’t have to type “adfs” in a prompt every time Windows is being shut down.  Also a registry change, this time we’re adding a key instead of modifying an existing one but it’s essentially the same process.

  New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT" -Name "Reliability" -Type "RegistryKey"

  New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" -Name "ShutdownReasonOn" -Value "0" -PropertyType dword

This one is to disable the loopback check.  If you’re not familiar with the problem, it is caused when attempting to authenticate against a site being hosted on the same machine you’re browsing from if the URL is not the same as the computer name.  You’ll be challenged for credentials and no matter what you enter you’ll be prompted 3 times and then denied access.  This is a symptom of a security feature to help prevent certain types of reflection attacks that you probably won’t care about in your development environment so here is the command you can use to turn it off.

  New-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Lsa" -Name "DisableLoopbackCheck" -Value "1" -PropertyType dword

  Restart-Computer

To rename my computer to something a little more useful than a randomly generated name I use this command.

  $ComputerName = “MyComputerName”

  $computer = Get-WmiObject Win32_ComputerSystem

  $computer.Rename($ComputerName)

  Restart-Computer

I also tend to need my development box to run as a domain controller.  To promote my server to a DC I use the following command.

  $DNSName = “DEV001.NET”

  $NetBiosName = “DEV001”

  $SafeModeAdminPassword = “pass@word1”

  dcpromo /unattend /ReplicaOrNewDomain:Domain /NewDomain:Forest /NewDomainDNSName:$DNSName /DomainNetBiosName:$NetBiosName /InstallDNS:Yes /RebootOnCompletion:No /SafeModeAdminPassword:$SafeModeAdminPassword

  Restart-Computer

So that is all I’ve got for this article.  I hope this was useful for some of you.

 

Posted: Mar 14 2012, 05:29 by Michael.Thompson | Comments (2) RSS comment feed

Tags: , ,
Categories: General | Portals & Collaboration

Dynamics CRM Q2 2012 Release

The next major release of Microsoft Dynamics CRM 2011 will be the second quarter of 2012. Currently available is Roll-Up 6 for CRM 2011. This next release will have some major functionality enhancements as part of the release. The primary theme of the release is "CRM Anywhere". This will include but not be limited to:

1. Groundbreaking Mobility (better mobile applicable enabled by CWR technology). This will now be available on many mobile platforms. That being said, there will be an additional fee from MS to enable this new mobile client.

2. Cross Browser Support (Safari Firefox and Chrome) well overdue but happy to have it finally!

3. Enhanced Social Capabilities (leveraging the RU5 What's New area and more)

4. General Enhancements (this includes self-service BI capabilities and more)

You can read all the details in the attached document. This release should be easy to enable and have many nice bells and whistles.

ReleasePreviewGuide.pdf (910.83 kb)

Posted: Mar 12 2012, 05:46 by Robert.Fitzer | Comments (5) RSS comment feed

Tags: ,
Categories: Dynamics CRM | Mobility

A PowerShell Script to Restart Computer and Continue Execution

So originally I had set out to write an article about setting up a SharePoint 2010 development environment.  I know it’s been written about ad nauseam but thought it would be a great way to get my feet wet in the blogging world.  Anyway as usual I got myself distracted so this article is not about building a SharePoint 2010 development environment.  It’s about one piece of that puzzle, specifically a simple pattern to automate the executing, restarting, and continuation of a PowerShell script.

So when I started I had one goal.  I wanted to be able to take a fresh installation of Windows Server 2008 R2 and with one click install and configure all of my development tools.  At the time I knew next to nothing about PowerShell but I was pretty sure it was going to be the correct tool.

The first hurdle I had to get over, and the one this article focuses on, was how to get my PowerShell script to restart the box and then continue where it left off.  Some quick searching got me headed in the correct direction.  Basically you can get PowerShell to write to the registry and by doing so you can get it to add itself to the Windows startup routine.  With some clever passing of parameters you have the ability to get the script to start where it left off.  Here’s a stripped down version of the code:

param($global:Step=1)

function Set-NextStep() {

  $global:Step++

  Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "SystemSetupStep" -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe &C:\Setup.ps1 -Step $global:Step"

}

function Run-NextStep() {

  switch ($global:Step) {

    1 { Step-1; break; }

    2 { Step-2; break; }

    default { Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "SystemSetupStep" }

  }

}

function Step-1() {

  Write-Host "This is step 1"

  Set-NextStep

  Restart-Computer

}

function Step-2() {

  Write-Host "This is step 2"

  Set-NextStep

  Run-NextStep

}

Run-NextStep

 

Ok, so let’s break this thing down.  First line is the $Step parameter.  This allows me to pass a value into the script when I call it from the command line or if I omit the parameter it sets itself to 1.

Next is the function Set-NextStep().  It increments the parameter $Step by 1 and then writes some information to the registry.  Specifically it adds an entry that tells Windows to execute a command on startup.  What is that command?  I’m asking it to start the PowerShell command window and to load my script as well as pass in the parameter $Step which would now be one value greater than when I started.

The Run-NextStep() function is simply a switch statement driven by the value of the parameter $Step.  What I really like about this is how well it documents what is actually going on.  It clearly shows you what steps are going to be executed and in what order.  The default behavior of the switch statement is some cleanup of the registry to remove the command that tells Windows to execute the script on startup.

Then I have two sample functions in there.  Step-1() outputs to the command window and then sets the next step before it restarts the computer.  Step-2 outputs to the command window sets the next step and runs the next step.

And that is that.  A simple little pattern to execute multiple functions in PowerShell over as many reboots as you need.  In the future maybe I’ll post my full blow version of this that actually does build a SharePoint 2010 Development box.

 

 

Posted: Mar 07 2012, 04:28 by Michael.Thompson | Comments (9) RSS comment feed

Tags: ,
Categories: General | Portals & Collaboration

Tidy up your JavaScript (CRM JScript)

 

Ah, JavaScript...we all love to hate it. But the fact is that JavaScript is one of the most popular programming languages in the world and its widespread use will continue to grow with the increased use of popular JavaScript libraries like jQuery. It is the language of the web. JScript, Microsoft's implementation (really just a different name) of JavaScript, is an integral part of developing and extending the capabilities of MS CRM. CRM 2011 is particularly geared toward the extended use of JavaScript with the inclusion of web resources and the REST endpoint/Odata web service architecture. The recommended (and provided SDK samples) interaction with the new REST endpoint is with the jQuery library and using JSON formatted data, both of which are JavaScript based.

 

Given this popular and ever growing use, there are a few (many actually) things that should be taken into consideration when writing JavaScript code. Douglas Crockford provides an extensive and in-depth discussion of these items in his book "JavaScript: The Good Parts". I would highly recommend this book for anyone who works with JavaScript frequently. One of my favorite quotes from the book is this:  

 

"The amazing thing about JavaScript is that it is possible to get work done with it

without knowing much about the language, or even knowing much about programming." 

 

But this ease of use doesn't necessarily mean that the code will be consistently reliable or will be using best practices. This is very true with its use in MS CRM...there is plenty of information available on the web and just about anyone can copy-n-paste a code snippet or two and bam you are programming! But there are risks with proceeding this way, as there are many code samples out there that may not be written with best practices in mind. To help mitigate this risk, I would like to discuss a couple easy items that can help clean up your JavaScript and may save time troubleshooting and tracking down mysterious issues.  

 

Here's a couple key points that I'd like to talk about to get you started with better JavaScript code: 

 

 

  • Declaring variables, not using the var keyword

Common practice (at least sometimes):

  • myVariable = "Jerry";

 

Best practice:

  • var myVariable = "Jerry";

 

Why: JavaScript lets you use a variable even when you have not explicitly declared it.  However, this makes the variable a global variable, which means that it is accessible to the program outside of the scope in which it is being used.  This can easily lead to naming conflicts and unintentional reassignment of a variable's value.  Bottom line: always declare variables with the var keyword.

 

  • Placement of curly braces {}:

 

Common formatting, especially for C# users, is to format a statement like so: 

If (test)

{

//code here

 

Best practice:

If (test) {

//code here

 

Why: JavaScript will try to automatically correct and insert semicolons where it thinks they belong. In certain situations, this can yield incorrect results, like when trying to set an object's value and it is inadvertently turned into a statement when a semicolon is automatically added. 

 

  • Testing for equality and inequality

Common practice:

  • == to test for equality
  • != to test for inequality

 

Best Practice:

  • === to test for equality
  • !== to test for inequality

 

Why: When testing for equality or inequality, if the operands (the objects being compared) are of a different type, then == may yield incorrect results in certain instances. For example, ' ' == 0, will return true, when in fact it should be false. The === will correctly return false. Bottom line: use === and !== instead of == and !=. 

 

Yeah, I can hear it now…"these things are so uncommon that they're not even worth talking about". But, like constructing a good building, its better to start with a strong foundation than to continue building upon minor mistakes, which over time may cause it to collapse. And furthermore, if the JavaScript guru Crockford says it’s a good idea, well, then I will faithfully follow in pursuit! Happy coding.

Posted: Feb 17 2012, 05:53 by Jerry.Fetzer | Comments (44) RSS comment feed

Tags:
Categories: Dynamics CRM

Tags

Categories

Archive

Blogroll

Neudesic Social Media

Follow Neudesic on Twitter Follow Neudesic on Facebook Neudesic Neudesic