Quick and Dirty Scripting

A blog that focuses on automating system administration tasks for Linux, Windows, and VMware ESX

Monday, January 4, 2010

 

Moving Day

I've move my blog to WordPress. The new URL is http://quickanddirtyscripting.wordpress.com/

Saturday, November 7, 2009

 

SharePoint Trending with Google APIs

At my day job, I manager a fairly large SharePoint deployment. We currently have about 450GB of content spread across 6-7 content databases. Needlessly to say, because SharePoint stores all of its information inside SQL Server database and Microsoft’s recommendation for no larger than 100GB content databases, trending our growth is very important to our team. For a while, we have been taking snapshots of the content database’s size, storing the information in Excel and generating Pivot table and charts. This worked fine but I wanted better. I’ve always liked the way Google’s Finance pages looked so I was excited to learn that you can use those same APIs in your own site. I think I’ve come up with a pretty neat solution by utilizing the SharePoint object model ,PowerShell, a little jQuery, Google’s APIs and an awesome course from EndUserSharepoint (Url - http://www.endusersharepoint.com, Twitter - @ESUP) . I’m always open for suggestions or improvements so after you read my posting, please let me know what you think.

Enough chit chat. Let’s get into how this works. To get this working for you, you’ll need three things:

  1. A SharePoint List to store the size of the SharePoint databases
  2. A PowerShell script to populate the list on a nightly basis
  3. A Web Part page with a Content Editor Web part + jQuery and the SharePoint List to generate the graph

SharePoint List

This easiest thing to create. Its just a custom SharePoint list with the following columns.

  • Web Application – the name of the SharePoint web application. I typically rename the default Name field.
  • Database Name – the name of the of the Content Database.
  • Database Size – a number field that will store the size of the database. The one thing that I do not like about the number field is that it inserts commas into the number. I debated about making this a string field but I decided to just handle this with a little jQuery
  • Date – this will hold the date the database snapshot was taken. I give it a default value of today’s date and only select date, not date/time.

A couple other things to note about the list:

  1. You will need to grant your SharePoint farm account contribute access to the list. This is because the PowerShell script will run as the Farm account. In a locked down environment like mine the only account that can access the databases is the Farm account.
  2. To keep the list from growing too large, I setup an IRM policy to delete a list item 90 days after the item is created. This keeps my list nice and trim automatically.

PowerShell Script

The PowerShell script utilizes the SharePoint object model so it has to run on one of the SharePoint servers in your farm. I choose my Central Admin server but it can be any server in the farm. The idea of the script to is to loop through every Web Application and gather the size of each Content Database associated with it and then upload the information to the SharePoint list. Its pretty straight forward except for one issue with SharePoint Content Database names when working with PowerShell. This discussion thread goes into good detail on how to get around it - http://groups.google.com/group/microsoft.public.windows.powershell/browse_thread/thread/16c06d92b2385325.

   1: . ..\SharePointFunctions.ps1
   2:  
   3: #Powershell had an issue with the SPContentDatabase assembly function Name and DiskSizeRequired. This is a work around
   4: #See http://groups.google.com/group/microsoft.public.windows.powershell/browse_thread/thread/16c06d92b2385325 for more information 
   5: $nameMethod = [Microsoft.Sharepoint.Administration.SPContentDatabase].getMethod("get_Name")
   6: $diskMethod = [Microsoft.Sharepoint.Administration.SPContentDatabase].getMethod("get_DiskSizeRequired")
   7:  
   8: set-variable -option constant -name TrendingList -value "Trending-SPDatabase"
   9: set-variable -option constant -name TrendingSite  -value "http://tis-collaboration.jpmchase.net/ti/shareddotnet"
  10: set-variable -option constant -name WebApplication  -value "Web Application" 
  11: set-variable -option constant -name DatabaseName -value "Database Name"
  12: set-variable -option constant -name DatabaseSize  -value "Database Size (MB)"
  13:  
  14: $MB = 1024*1024
  15:  
  16: #Function main - Where all of the fun is
  17: function main() {
  18:  
  19:     #Loop through all WebApplications
  20:     get-SPWebApplications -name * | % {
  21:         
  22:         #Don't need to display information on the Central Admin site
  23:         if( -not $_.IsAdministrationWebApplication ) {
  24:                         
  25:             $strWebApp = $_.Name.TrimStart()
  26:                     
  27:             #Display information about the content database attached to this WebApp
  28:             $ContentDatabaseCollection = $_.ContentDatabases
  29:             
  30:             $ContentDatabaseCollection | % {
  31:     
  32:                 $newlistitem  = @{}
  33:                 $newlistitem[$WebApplication] = $strWebApp
  34:                 $newlistitem[$DatabaseName] = $nameMethod.Invoke($_, "instance,public", $null, $null, $null)
  35:                 $newlistitem[$DatabaseSize] = [math]::round( ($diskMethod.Invoke($_, "instance,public", $null, $null, $null) / $MB), 0)
  36:                 
  37:                 add-toSpList -url $TrendingSite -list $TrendingList -entry $newlistitem 
  38:  
  39:             }
  40:         }
  41:     }
  42:  
  43: }
  44: main

The variables TrendingSite, TrendingList, WebApplication, DatabaseName, DatabaseSize will need to be updated for your environment.


The function get-SPWebApplication will return a list Web Application objects. You can limit the list to a specific Web Application by replacing “–name *” with “–name $name_of_web_application”



   1: function get-SPWebApplication( [string] $name )
   2: {
   3:     $WebServiceCollection = new-object microsoft.sharepoint.administration.SpWebServiceCollection( get-SPFarm )
   4:     $WebServiceCollection | % { $WebApplications += $_.WebApplications }
   5:     
   6:     return ( $webApplications | where { $_.Name.ToLower() -like "*"+$name.ToLower()+"*" } | select -Unique )
   7: }
   8:  
   9: function get-SPFarm()
  10: {
  11:     return [microsoft.sharepoint.administration.spfarm]::local
  12: }

To add the information to the SharePoint List, we have to pass a hash table with name/value pairs to the function add-toSpList. We also have to pass the URL of the Site that hosts the list and the List name.



   1:  
   2: function add-toSpList ( [Object] $url, [string] $list, [HashTable] $entry)
   3: {
   4:     if( $url.GetType().Name -eq "String")
   5:     {
   6:         $site = new-object Microsoft.SharePoint.SPSite($url)
   7:         $web = $site.OpenWeb()
   8:     }else
   9:     {
  10:         $web = $url
  11:     }
  12:     
  13:     $splist = $web.Lists[$list]
  14:     $newitem = $splist.items.Add() 
  15:  
  16:     $entry.Keys.GetEnumerator() | % {
  17:         $newitem[$_] = $entry[$_]
  18:     }
  19:     
  20:     $newitem.update() 
  21:     $web.Dispose()
  22:     $site.Dispose() 
  23: }

Now that we have to entire script, all you have to do is schedule it to run as often as you wish via the Windows Scheduler. Make sure you run the script as the SharePoint Farm Admin.


SharePoint WebPart Page + jQuery


Now the fun begins. I owe a ton of thanks to the EndUserSharepoint.com team as I used a lot of their work as a basis for what I’m trying to do. First, what you need to do is add a Web Part page to your site. On this page, add a Content Editor Web Part and the SharePoint List that you created earlier. For the list, I filtered the view to only display one database name. I also only selected he database name, size, and date columns. If I have more than one database (or web application) then I will need to create a page for each. The view should look something like this:






Next you have to get jQuery working with your site, and this means adding code to the Content Editor Web Part. I would suggest heading over to EndUserSharepoint.com (http://www.endusersharepoint.com/?s=jquery+for+everyone) for more information on jQuery, but basically jQuery is a collection of JavaScript functions and objects that make JavaScript coding a lot easier. You need to load the jQuery engine and thankfully Google hosts it for you. All you need to do is link to their URL in your code. This is what I done for this to work. Google also hosts the visualization APIs that we need to generate the graph. If you want to learn more about the available APIs and samples on how to use them, head over to http://code.google.com/apis/visualization/


The code for the Content Editor Web Part + jQuery is as follows: (PS: This has to be added via the CEWP’s source button, not the Rich Text Editor.)



   1: <script type="text/javascript" src="https://www.google.com/jsapi"></script>
   2:  
   3: <script type="text/javascript">
   4:     google.load('visualization', '1', {packages: ['annotatedtimeline']});
   5:     function drawVisualization() {
   6:       var dataList = $("td.ms-vb2 div");
   7:       var dateList = $("td.ms-vb2 nobr");
   8:       var dateArray = new Array();
   9:  
  10:       $.each(dateList, function(i,e)
  11:       {
  12:         var y = $(e).text();
  13:         dateArray[i] = y;
  14:       });
  15:  
  16:       var data = new google.visualization.DataTable();
  17:       data.addColumn('date', 'Date');
  18:       data.addColumn('number', 'Size');
  19:       data.addRows(dataList.length);
  20:       
  21:       $.each(dataList, function(i,e)
  22:       {
  23:          var x= $(e).text().replace(",","");
  24:          data.setValue(i, 0, new Date(dateArray[i]) );
  25:          data.setValue(i, 1, parseInt(x * 1048576) );
  26:       });
  27:       
  28:       var annotatedtimeline = new google.visualization.AnnotatedTimeLine(document.getElementById('trending'));
  29:       annotatedtimeline.draw(data, {'displayAnnotations': true});
  30:     }
  31:     
  32:     google.setOnLoadCallback(drawVisualization);
  33:  
  34: </script>
  35: <div id="trending" style="width: 100%; height: 400px;"></div>

Well there you go. Once you have everything in place you should have a page the looks similar to this:

















I hope that everyone has enjoyed this post and I always look forward to any feedback.

Thursday, October 15, 2009

 

Time zone Clocks with SharePoint using jQuery

I work with a lot people who are spread across the global so we are always trying to find out what is their local time. While Google has a great query that will convert time for you, I wanted to see if I could create a SharePoint page that will list the times in all the zones that we have people on the team. I found a great blog post on ‘Path to Sharepoint’ (http://pathtosharepoint.wordpress.com/2009/01/09/world-clock/) did exactly what I wanted. The one catch was that he used a lot of JavaScript to convert the calculated text column to HTML. I wondered if jQuery could make it easier. This is what I came up with . . .

  1: <script type="text/javascript" src="http://www.google.com/jsapi"></script>
  2: <script type="text/javascript">
  3:     //load jQuery
  4:     google.load("jquery", "1.3.2");
  5: </script>
  6:
  7: <script type="text/javascript">
  8: $(function() {
  9:     $("td.ms-stylebody:contains('DIV')").each(function(){
 10:         $(this).html($(this).text());
 11:     });
 12: });
 13: </script>

There are a couple requirements for this to work.



  1. The list view was set to boxed.
  2. The calculated column had this equation:
  3. ="<DIV id='calchtml'><embed src='http://www.clocklink.com/clocks/5001-blue.swf?TimeZone="&TimeZone&"'
    width='250' height='70' wmode='transparent' type='application/x-shockwave-flash'></DIV>"

If you follow the instructions at ‘Path to SharePoint’ and use this jQuery you should get something like this:




Wednesday, April 9, 2008

 

Powershell Script to Create Sharepoint Web Application

Microsoft Sharepoint 2007 has a very nice web-based UI to create Web Applications, but there is one catch. The UI creates the backend database on the fly. In a lot of companies, this is the role of a DBA, and only they are allowed to create a new database. In a way this makes sense due to the fact that the DBAs must maintain the intergity of the database server. Microsoft has provided a switch within the stsadm command to create a new Web Application with an existing database, but I thought I could come up with a way to do it using Powersell.

What I wanted to do was to supply a script with an XML configuration that would create 1-N Web Applications with 1-N Sites. The XML looks like the following:
<Sharepoint>
<WebApplication name="Test Site #1" hostheader="http://portal.example.net">
<AppPoolName>WebApp-Portal</AppPoolName>
<AppPoolUser>Domain\myServiceAccount</AppPoolUser>
<AppPoolPass>test1235</AppPoolPass>
<Port>80</Port>
<DatabaseServer>DBS-SERVER-NAME\MOSS</DatabaseServer>
<DatabaseName>WSS_Content_Potal</DatabaseName>
<RootDirectory>d:\inetpub\wwwroot\wss\portal</RootDirectory>
<Sites>
<Site Path="/">
<Title>Root Site</Title>
<Description>Root Site</Description>
<Type>STS#1</Type>
<AdminAccount>DOMAIN\Administrator</AdminAccount>
<AdminName>Administrator1</AdminName>
<AdminEmail>root@jexample.net</AdminEmail>
</Site>
</Sites>
</WebApplication>
<WebApplication></WebApplication>
</Sharepoint>


The first thing that needs to be done is load the Sharepoint .NET assembly and attach to the local farm


[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [microsoft.sharepoint.administration.spfarm]::local


Then load the XML configuration file and loop through all Web Applications.


function main()
{

$moss = "Sharepoint"
$cfg = [xml](gc $cfgFile)

if( $? -eq $false ) {
Write-Host "Could not cleanly parase XML file. Exiting . . ."
return $false
}

Write-Host "Found Sharepoint Farm on Local Host . . "

Write-Host "Using $cfgFile file . . ."

$cfg.$moss.WebApplication | % {

createWebApp( $_ )

}
}

main


Surprisingly to create new Web Application, all you need to do is create a Powershell object of type SPWebApplicationBuilder, assign the proper variables, create and then provision. The command can take up to 10 minutes to complete so be patient.


function createWebApp( [object] $cfg )
{
$webAppBuilder = $nul
$webAppBuilder = new-object _
microsoft.sharepoint.administration.SPWebApplicationBuilder($farm)

$secureString = ConvertTo-SecureString $cfg.AppPoolPass -asPlainText -force

$webAppBuilder.Port = $cfg.port
$webAppBuilder.ApplicationPoolId = $cfg.AppPoolName
$webAppBuilder.ApplicationPoolUsername = $cfg.AppPoolUser
$webAppBuilder.ApplicationPoolPassword = $secureString

$webAppBuilder.HostHeader = $cfg.hostheader
$webAppBuilder.ServerComment = $cfg.name
$webAppBuilder.DatabaseServer = $cfg.DatabaseServer
$webAppBuilder.DatabaseName = $cfg.DatabaseName
$webAppBuilder.RootDirectory = $cfg.RootDirectory

if( $cfg.AllowAnonymous.ToString().ToLower() -eq "true" ) {
$webAppBuilder.AllowAnonymousAccess = $true
}

Write-Host "Will now Provision this Web Application."
Write-Host "This may take up to 10 minutes. . ."
$webApp = $webAppBuilder.Create()
$webApp.Provision()


Finally, loop through all Sites that are listed in the XML and add them to the Web Application.

$cfg.Sites.Site | % {
$title = $_.SiteTitle.ToString()
$path = $_.Path.ToString()

rite-Host "Will now Provision Site - $title ($path). "
Write-Host "This may take up to 10 minutes. . ."

$webApp.Sites.Add( $_.Path,
$_.Title,
$_.Description,
1033,
$_.Type,
$_.AdminAccount,
$_.AdminName,
$_.AdminEmail)
}

See easy as 1, 2, 3 . . .

Sunday, February 10, 2008

 

Powershell Scriptblocks

One of the interesting features that is, I believe, little know in Powershell is the scriptblock. What is a scriptblock? The way that I define it is a function or block of code that is assigned to a variable. Why do you need to know about this? I've found it most useful when using Powershell to create a GUI. You have to assign an action to an element on the GUI (like a Button). But the action can not be a function. It must be a script block. You can define the block as a variable or define it within the {} of the Add_Click method of the System.Windows.Forms.Button object.

Now I'm probably completely wrong with this, and if I am I would love to learn more about it. So please let me know! I want to cover how to create GUIs in Powershell in couple weeks so I will be building off this blog post.

So how do you create a Script block. Its easy as assigning a variable to a anything within {}.

A simple case would be something like
PS C:\temp> $x = { Write-Host "Hello, World!" }

PS C:\temp> $x.GetType()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ScriptBlock System.Object



If you just call the variable, it will return the block definition

PS C:\temp> $x
Write-Host "Hello, World!"

To invoke the scriptblock, you need to call the Inovke() method of the script

PS C:\temp> $x.Invoke()
Hello, World!


Within the scriptblock, you can do whatever you want to do. It can be multiple lines, you can even call functions from within the block. For example:

PS C:\temp> function y () {
>> Write-Host "Within function y"
>> }
>>
PS C:\temp> y
Within function y
PS C:\temp> $x = {
>> Write-Host "Within scriptblock x"
>> y
>> }
>>
PS C:\temp> $x.Invoke()
Within scriptblock x
Within function y

Here are all of the methods that Scriptblocks provides within Powershell

PS C:\temp> $x | Get-Member


TypeName: System.Management.Automation.ScriptBlock

Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_IsFilter Method System.Boolean get_IsFilter()
Invoke Method System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, Syst...
InvokeReturnAsIs Method System.Object InvokeReturnAsIs(Params Object[] args)
set_IsFilter Method System.Void set_IsFilter(Boolean value)
ToString Method System.String ToString()
IsFilter Property System.Boolean IsFilter {get;set;}


I hope this gives you an introduction into scriptblocks. Next time, hopefully, we can cover the TCL/TK-like feature of Powershell - its GUI.





Wednesday, January 16, 2008

 

Q&D Ping Function for Powershell

By default, Powershell does not have any built in functionality to send an ICMP ping to see if a remote computer is alive on and on the network. But never fear because Powershell is based off .NET. This means it is very easy to implement a Ping function that you can add to a Powershell library file (along with other functions that I have blogged about previously). The function is pretty straight forward. All that you really new to do is to create two .NET objects - Ping and PingReply. My ping function only looks to see if a success was returned by ICMP. I am not looking for round trip time or to handle non-successful.



function Ping ( [string] $strComputer )
{
$timeout=120;
trap { continue; }

$ping = new-object System.Net.NetworkInformation.Ping
$reply = new-object System.Net.NetworkInformation.PingReply

$reply = $ping.Send($strComputer, $timeout);
if( $reply.Status -eq "Success" )
{
return $true;
}
return $false;

}


Again that's it. The trap command will just ignore any errors that are throw by the $ping.Send command. If anyone has questions than you can always email me at brian@bjd145.org.

Sunday, January 6, 2008

 

Powershell and XML Configuration Files

 

One of the things that I almost always create when I am developing a script is a configuration file. Utilizing XML for a configuration file format is very easy with Powershell.  The first thing that you need to do is to layout your XML file. I typically don't create a DTD or verify the XML schema because well, only my scripts will be using the XML configuration file.  Not completely within spec, but I've learn to live with it. The XML does need to be well formed with proper closing brackets, root node, etc in order for Powershell to read the XML but that is the only restriction.

The most common XML file that I create includes a list of servers and some attribute about those servers. A simple  example that of a test application that is made up of three web servers and a database server. The way that I would create the XML would be as follows:

<test_app_servers>
    <dbs server="vm-dbs1" dbsname="eut"/>
    <web dir="d$\inetpub\wwwroot">
        <server>vm-test1</server>
        <server>vm-test2</server>
        <server>vm-test3</server>
    </web></test_app_servers>

Now the important part, how does Powershell read the XML.  The code is very straight forward. You use the get-content command and cast the file as XML.  The command is:

$cfgFile = ".\test_app_config.xml"
$root = "test_app_servers"
$cfg = [xml] ( gc $cfgFile )

Now once you have the configuration saved in the variable $cfg then how do you use it?  Powershell stores the information in a tree format so it is easy to get at. Some samples of how to get at the data include:

$strDbsName = $cfg.$root.dbs.dbsname

$strDbsServer = $cfg.$root.dbs.server

$strWebDir = $cfg.$root.web.dir

$cfg.$root.web.server | % {
        $strWebServer = $_
        "Content of
\\$strWebServer\$strWebDir"
        dir
\\$strWebServer\$strWebDir

}

And that is all you should need to know in order to utilize XML as a configuration file with Powershell

Labels: ,


Thursday, December 27, 2007

 

Update to VMware's Powershell Toolkit

 

Just wanted to throw this out there for anyone using VMware's Powershell toolkit. They released a new CTP on 12/26. The file version is 68764.  They've added a bunch of new commands such as Get-Inventory, New/Get-OSCustomizationSpec, Add-VMHost, Move-Datacenter, Move-Cluster, Find-EntityViews, etc . .

I need to find some time to play with these new commands. I do know that the base Get-VIServer and Get-VMHost do work with ESX 3.5 which is nice to see as well.

Until I have more scripts to share. . .

Labels: ,


Archives

October 2007   November 2007   December 2007   January 2008   February 2008   April 2008   October 2009   November 2009  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]