Quick and Dirty Scripting

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

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: ,


Sunday, December 2, 2007

 

How to Find all Domain Computers Using Powershel

I'm asked a lot to find out something from all the servers in my Active Directory domain. Like "Tell me the free space on all servers in the domain" or "List all of the error events from Microsoft's Event Viewer for servers in the domain." These two questions can be answered very easily using WMI and Powershell,namely the get-WMIobject function. But how to get all of the server names? Using VBScript, this can be accomplished by using ADSI. But the code is hard to follow and I never really got a hang of using the ADSI calls. But why uses that when there is a great tool made already. dsquery. dsquery is tool written by Microsoft to query Active Directory (http://technet2.microsoft.com/WindowsServer/en/Library/46ba1426-43fd-4985-b429-cd53d3046f011033.mspx). Using this and combined with Powershell is Quick and Dirty but as always gets the job done.

So how do you use it? First the basic . . .

dsquery computer "ou=Domain Controllers,DC=example,DC=com" will find all servers in the Organizational Unit Domain Controllers. dsquery has a option 'server' which will do the same thing (dsquery servers)

The return result is . . .
"CN=DC03,OU=Domain Controllers,DC=example,DC=com"
"CN=DC02,OU=Domain Controllers,DC=example,DC=com"
"CN=DC01,OU=Domain Controllers,DC=example,DC=com"

To return all computers, just do ....
dsquery computer -limit 0 - The limit 0 tells dsquery not to limit the number of systems it returns. I believe the default is 100.

dsquery can also be used to find servers in the domain . . . .
dsquery computer -name "*WEB*" will return
"CN=DRWEB01,OU=Web,OU=Disaster Recovery,OU=Machines,OU=Shared Applications,DC=example,DC=com"
"CN=DEV-WEB01,OU=Development,OU=Machines,OU=Shared Applications,DC=example,DC=com"
"CN=STG-WEB01,OU=Stage,OU=Machines,OU=Shared Applications,DC=example,DC=com"
"CN=STG-WEB02,OU=Stage,OU=Machines,OU=Shared Applications,DC=example,DC=com"
"CN=PRD-WEB01,OU=Production,OU=Machines,OU=Shared Applications,DC=example,DC=com"


So can this be used with Powershell. As long as dsquery is located in the path where powershell can call it from, just write a function such as this

function GetDomainServers {
return @( dsquery computer -limit 0 -o rdn | % { $_.Split("`"")[1] } )
}
The -o rdn means to just return the server name. The server name will be in quotes which is why the return values are piped into Split("`""). This will split the server name into 3 parts. We just want the second value. This is combined to form an array which is returned.

In other words,
dsquery computer -limit 0 -o rdn returns "PRD-WEB01".
The powershell command Split("`"") will split the value on ".
This returns
"
PRD-WEB01
"

To call this function, its as easy as $servers = GetDomainServers.

Well that's it. As always, I would love to hear feed back from anyone who finds these posts interesting and helpful.

Wednesday, November 28, 2007

 

Restoring A Database with transaction logs

I've been often asked to restore a database to another server and have all transactions logs applied to it up to a certain point. Using SQL Server 2005, you could setup Transaction Log shipping and it works very well, or you could copy the files over manually then apply them manually using SQL Server Enterprise Studio. Well I'm lazy so I came up with this simple script to do the work for me . . .

This script will ask for the database name to be restored as well as the directory where the transaction logs , drop any existing database of that name, restore the database and its transaction logs. It will not setup your database backups (full or transaction) nor will it replicate them over. I do have scripts for this but a tool like SyncBackSE works just as well. Oh this is a Windows batch script that utilizes osql which comes with SQL Server 2005.

@echo off

set BAKFILE=%1
set DBSNAME=%2
set LOGDIR=%3

IF NOT DEFINED BAKFILE (GOTO HELP)
IF NOT DEFINED DBSNAME (GOTO HELP)
IF NOT DEFINED LOGDIR (GOTO HELP)

echo Using the following variables to restore the database
echo Database Backup file - %BAKFILE%
echo Database Name - %DBSNAME%
echo Trans Log Directory - %LOGDIR%
choice /M "Do you want to continue"

IF ERRORLEVEL 2 ( GOTO END)

set DATADIR=d:\DATA
set SQL=%TMP%\restore.sql
set OUT=.\restore_results.txt

IF EXIST %SQL% ( del %SQL% )

IF NOT EXIST %BAKFILE% (
echo %BAKFILE does not exist. Must exit.
GOTO END
)

echo USE [MASTER] > %SQL%
echo GO >> %SQL%
echo ALTER DATABASE %DBS% SET SINGLE_USER WITH ROLLBACK_IMMEDIATE >> %SQL%
echo GO >> %SQL%
echo DROP DATABASE %DBS% >> %SQL%
echo GO >> %SQL%

echo RESTORE DATABASE [%DBSNAME%] > %SQL%
echo FROM DISK = '%BAKFILE%' >> %SQL%
echo WITH NORECOVERY >> %SQL%
echo GO >> %SQL%

for /f "tokens=* delims= " %%a in ('dir /b /a-d /od %LOGDIR%\*.trn') do (
echo RESTORE LOG [%DBSNAME%] >> %SQL%
echo FROM DISK = '%LOGDIR%\%%a' >> %SQL%
echo WITH NORECOVERY >> %SQL%
echo GO >> %SQL%
set LAST=%%a
)

echo RESTORE LOG [%DBSNAME%] >> %SQL%
echo FROM DISK = '%LOGDIR%\%LAST%' >> %SQL%
echo WITH RECOVERY >> %SQL%
echo GO >> %SQL%

osql -E -w 5000 -i %SQL% -o %OUT%
GOTO END

:HELP
echo.
echo Usage: restore_dbs.bat [BAKFILE] [DBSNAME] [LOG DIR]
echo.
echo.
echo BAKFILE - The full path to a recent backup file of the database
echo DBSNAME - Name of the new database
echo LOG DIR - The full path to a directory that contains all the recent tanslogs
echo *The LOG DIR should not contain any transaction logs older than BAKFILE
echo.
echo Eg: restore.bat d:\Backups\SampleDBS_backup_200705222201.bak SampleDBS d:\Temp
echo.

:END
IF EXIST %SQL% ( del %SQL% )

Labels:


Archives

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

View Brian Denicola's profile on LinkedIn

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

Subscribe to Posts [Atom]