Automated Datacentre Deployment – It’s Time to Let Go of Your Physical Devices
I work in the process automation arena, and have mainly spent the last 6 months automating server build processes. You might think "Why have you been doing that? There are plenty of tools out there for it", well, you might be right…
In fact you are right. The problem that I came up against is that server build automation tools assume that there is a physical server with drives and network already setup that it can build or there is a virtual environment/"cloud" that it can start orchestrating. Can you see where the problem is yet?
Best of breed datacentre environments use dedicated physical devices delivering what they're designed for. Devices such as network switches and routers, firewalls and IPS/IDS devices, SAN and NAS technology, fabric networks, the list goes on.
All of these devices are made by the industry leaders, such as Cisco, HP, Broccade, Netapp etc, all who have their own configuration interfaces and run their own software/firmware, many don't even have an API…
So, when it comes to deploying an enterprise environment which will consist of a physical and virtual hybrid, you're not going to just be putting operating systems and applications on servers, you're going to be:
- Configuring access ports on network switches
- Configuring core network routing
- Configuring SAN or NAS storage and presenting it
- Configuring fabric networks
- Configuring physical server hardware via ILO or DRAC
All of the above are generally done by subject matter experts via SSH or a web interface or (if you’re lucky) via a vendor specific orchestration tool which will keep track of all the different command sets required by different firmware/software versions of their relevant devices.
The issue now comes in the fact that everyone is thinking "Cloud" with services such as "Auto Scaling" and "On-Demand", so they want virtual datacentre environments delivering performance and security of the old school environments but spun up quickly, very quickly, we're talking minutes here, not days or weeks.
This provides a big challenge in orchestrating the configuration of all of these physical devices. This is not to say it can't be done, it CAN be done, I've done it, but should we be doing it?
If you look at products like VCloud Director, CA (Previously 3Tera) Applogic and AWS you'll start to see what I'm talking about. These physical datacentre elements are now virtualised. For instance, you can drop a virtual F5 firewall, load balancer or some S3 storage into your AWS environment, and you're done. Just "Drag and Drop" not "Rack and Stack".
All of these devices are now software, but delivered by industry leading vendors. This is not to say they're better than their physical predecessors, but they're a lot easier to deploy in a virtual datacentre environment, and more importantly, you can automate their configuration.
So, it really feels like we're on the cusp of a datacentre revolution, and it's one that a lot of enterprises and MSP's aren't ready for. If you're going to compete in this growing, agile, scalable and transient world of "The Cloud", it's time to lose the shackles of physical devices and embrace an even more virtual world.
Extract Functions, Parameters and Help Info from a Module
Morning all! It's been a while since I've written a blog post, mainly due to swapping jobs around and moving house, but now I'm back and keen to get writing again, so here we go.
I've been working on various Powershell related projects which involve a central database storage solution for modules so we can share them easily internally in the business and use them as part of orchestration tools we've written. One of the things we needed to do was create an XML manifest of all functions in a module, their paramters and types and then (if it existed) the help for each paramter so we could pass it into the toolbox.
The following functions extract the data into nice Powershell objects so you can use them however you like.
The first function is Get-ModuleFunctionsandParams and does what it says on the tin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Function Get-ModuleFunctionsAndParams($ModuleName) { Import-Module $ModuleName $Functions = Get-Command -Module $ModuleName $FuncTable = @() ForEach($Func in $Functions) { $ARow = "" | Select Name,Params $ARow.Name = $Func.Name $Params = @() ForEach($PSet in $Func.ParameterSets) { ForEach($Param in $PSet.Parameters) { $BRow = "" | Select Name,Type $BRow.Name = $Param.Name $BRow.Type = $Param.ParameterType $Params += $BRow } } $ARow.Params = $Params $FuncTable += $ARow } Return $FuncTable } |
This is the Get-ParamterHelp function, you pass in the function name and the variable name you're interested in, and away you go....
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | Function Get-ParameterHelp($FunctionName,$VariableName) { $Help = Get-Help $FunctionName $Table = @() ForEach($Param in $Help.Parameters.Parameter) { if(-not $VariableName) { $ARow = "" | Select Name,Description,Position,Required $ARow.Name = $Param.Name $ARow.Description = $Param.Description[0].text $ARow.Position = $Param.Position $ARow.Required = $Param.Required $Table += $ARow } Else { if($Param.Name.ToString().ToLower() -eq $VariableName.ToString().ToLower()) { $ARow = "" | Select Name,Description,Position,Required $ARow.Name = $Param.Name $ARow.Description = $Param.Description[0].text $ARow.Position = $Param.Position $ARow.Required = $Param.Required $Table += $ARow } } } Return $Table } |
Get Volume Shadow Copy Information from Powershell
A colleague came and asked me yesterday if it was possible to get Volume Shadow Copy Information from a list of servers..... Powershell to the rescue!
The biggest problem that I found is that the data in the WMI class refers to the drive by it's device ID, so I've built in a lookup to translate that to a drive letter.
Here's the function I came up with, hope it can help you too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | Function Get-ShadowCopyInfo([string]$Computer,[string]$UserName,[string]$Password,[switch]$MB,[switch]$PromptForCredentials,[switch]$NoCredentials) { <# .Synopsis Get volume shadow copy information .Description Obtains volume shadow copy information for local and remote servers .Parameter Computer The name of the computer you wish to run the command against, default is LocalHost .Parameter UserName The UserName for any credentials you wish to pass .Parameter Password The Password for any credentials you wish to pass .Parameter MB Return stats in MB rather than GB .Parameter PromptForCredentials This will prompt you to enter credentials for the server, rather than passing in the data in plain text. .Parameter NoCredentials Forces the function to not use any credentials. .Example Get-ShadowCopyInfo This will return information about the shadow copy data on localhost .Example Get-ShadowCopyInfo -Computer ServerA -UserName "MyDomain\MyUser" -Password "MyPassword" This will return information about the shadow copy data on ServerA using the supplied credentials .Example Get-ShadowCopyInfo -Computer ServerA -PromptForCredentials This will return information about the shadow copy data on ServerA, but will prompt you to enter credentials. .Example Get-ShadowCopyInfo -Computer ServerA -NoCredentials This will return information about the shadow copy data on ServerA using no additional credentials. .Example Get-ShadowCopyInfo -MB This will return data about the shadow copy volume on your computer, but return the stats in MB rather than GB. .Notes Name: Get-ShadowCopyInfo Author: Peter Rossi Last Edited: 26th May 2011 #> if(-not $UserName) { if(-not $Password) { If(-not $PromptForCredentials) { $NoCredentials = $True } } } if(-not $Computer) { $Computer = "LocalHost" } if(-not $NoCredentials) { if(-not $PromptForCredentials) { $SecPass = convertto-securestring -asplaintext -string $Password -force $Creds = new-object System.Management.Automation.PSCredential -argumentlist $UserName,$SecPass } Else { $Creds = Get-Credential } } if(-not $NoCredentials) { $ShadowInfo = gwmi -Class win32_shadowstorage -ComputerName $Computer -Credential $Creds $VolumeInfo = gwmi -Class win32_volume -ComputerName $Computer -Credential $Creds } Else { $ShadowInfo = gwmi -Class win32_shadowstorage -ComputerName $Computer $VolumeInfo = gwmi -Class win32_volume -ComputerName $Computer } $Final = @() ForEach($Shadow in $ShadowInfo) { if(-not $MB) { $ARow = "" | Select Computer,Drive,SizeGB,ShadowMaxSizeGB,ShadowUsedSizeGB,ShadowPercent,TimeChecked } Else { $ARow = "" | Select Computer,Drive,SizeMB,ShadowMaxSizeMB,ShadowUsedSizeMB,ShadowPercent,TimeChecked } $ARow.Computer = $Computer $ARow.TimeChecked = Get-Date ForEach($Vol in $VolumeInfo) { If($Shadow.Volume -like "*$($Vol.DeviceId.trimstart("\\?\Volume").trimend("\"))*") { $ARow.Drive = $Vol.Name if($MB) { $ARow.SizeMB = "{0:N2}" -f ($Vol.Capacity/1024/1024) $ARow.ShadowMaxSizeMB = "{0:N2}" -f ($Shadow.MaxSpace/1024/1024) $ARow.ShadowUsedSizeMB = "{0:N2}" -f ($Shadow.UsedSpace/1024/1024) } Else { $ARow.SizeGB = "{0:N2}" -f ($Vol.Capacity/1024/1024/1024) $ARow.ShadowMaxSizeGB = "{0:N2}" -f ($Shadow.MaxSpace/1024/1024/1024) $ARow.ShadowUsedSizeGB = "{0:N2}" -f ($Shadow.UsedSpace/1024/1024/1024) } $Percent = ($Shadow.MaxSpace/$Vol.Capacity) * 100 $ARow.ShadowPercent = [int]$Percent } } $Final += $ARow } Return $Final } |
IT Process Automation (ITPA) – How I See It
I thought for this blog, I'd write something slightly different rather than just the usual Powershell piece.
I have been working in the IT Process Automation (ITPA) arena for around 2 years now. It's a fascinating industry that's starting to emerge, and as more and more large IT companies realise it's potential, the core concept is really starting to take hold.
The main idea behind it is automating processes which are generally highly repetitive, quite often long winded and are prone to user errors. Although, this does pose a large block in front of any manager when your staff ask: "But doesn't this mean we'll be out of a job?". It's the largest problem I've faced when trying to get various processes rolled out. Personally, I feel that by removing these time wasting tasks, the staff that you already have will have more time to work on interesting projects and further their skill sets and thus career. This is still a tough sell....
The main product that I have worked with from an workflow perspective is NetIQ Aegis, but I've also seen Symantecs Altiris workflow product and Microsoft's Opalis. These are great products, although, with one flaw. The way they are marketed is such that "Any business process owner can put together a workflow". Incorrect. I'd really like to see a business manager attempt to throw together a technical workflow without any prior IT experience. To deliver this ease, the approach tends to be to abstract the code from the user, and present them with thousands of buttons and options to click. The reality is that most users who will design workflows are going to be either DevOps or a developer. Both of which would much rather have keyboard access straight into editing variables etc. SQL SSIS seems to have gone slightly the other way with this, but I think I'd prefer that approach.
You may ask, "Well, if you want to just start typing out scripts, then why do you bother with workflow in the first place?". Well, workflow has a great deal to offer. It allows for a very robust process to be put together which in most cases can be easily watched, tracked and reported upon. This alone is worth a lot to most businesses.
The biggest topic next to workflow, is "How do you initiate workflow?"
The way most workflow products out there seem to work, relys on a manual button press from someone. Whether that's triggered through the built in UI, or via an API call or a database trigger from an 3rd party app, who knows. This is great if you want to load up your service desk with a thousand potential buttons to press in the event of something happening, but in most cases, that's not the best move....
Complex Event Processing (CEP) - This is the area where it's all moving to, and it's one of the key reasons why I've been working with NetIQ Aegis for the past 2 years. Aegis cleverly combines the StreamBase CEP engine, which allows you to watch for patterns in all of your data streams "while it is in flight", simultaneously. Therefore, you can configure a custom trigger to watch for something like:
In any 5 minute window
If you see a ping timeout from a server in data centre 1
AND you see a URL timeout for a domain in this group
AND you see a disk capacity warning for one of these servers
Trigger something.....
You can then have a workflow which specifically caters for that scenario. No users needed, it just gets on with it.
Unfortunately, there are a few occasions where you might want your workflow engine not to react i.e. during a planned maintenance window or during a major incident. The trick to this is designing your architecture so you can restrict the flow of data into your CEP engine at the right times, therefore, creating some form of data flow controller in front of it.... pie in the sky stuff though.
Either way, CEP is fantastic. Annoyingly, it's really marketed in the financial sector due to the fact it can watch for stock trade patterns etc and allow traders to make choices quickly. However, as you can see, there are some fantastic uses for it in a data centre environment.
So, what's the best of both worlds?
Well, as I mentioned above, I've been using NetIQ Aegis, and it was the product we picked for a lot of reasons, but mainly because of the CEP engine integration. The downside is that the StreamBase engine is wrapped by the product and you therefore don't get a huge amount of access to how it actually works, which can be quite frustrating sometimes... although, I know it's a Java based product and it's not overly easy to integrate with .net, so I'm not too worried.
As you all know, I'm a massive Powershell advocate and believe that there are boundless time savings to be had from it in large IT enterprises. However, imagine Powershell bolted to a decent CEP? Powershell that ran automatically when it knew something needed to be done.....
You can sort of do this with Aegis, but I'd like a much closer to the metal approach, and believe that this could easily be delivered by Microsofts new StreamInsight product.
StreamInsight is Microsofts new CEP engine, and it ships as part of SQL 2008 R2, although it's not actually a server product. If you get a license for it, you will receive 10 or 12 dll's which you can import into a .net c# project and build your own engine. You can connect to as many different data streams as you like, process them how you wish and then fire them off to any system you deem fit to deal with them all in real time. If you had a service which fired off Powershell at the end, then you'd be done.....
I'd love to see a product like this to come on the market, but I think we're a bit of a way off, so I'm currently working on my own.
If you're interested in looking at Stream Insight or CEP in general, Alan Mitchell did a great session on StreamInsight at SQLBits in 2010 and 2011, you can see his deep dive video here.
Hope you found this post interesting, and give me a shout if you're interested in process automation or CEP.
Get and Set – Active Directory User Thumbnail Photos
I was just asked how you can update the user thumbnail images in active directory (the ones that appear in outlook when you select a user etc). So I did some digging.
Basically, thumbnailPhoto is a property of an AD user which stores an array of bytes. So I've put together the following functions to allow you to easily get or set the images for a user:
Function Get-ADThumbnailPhoto() { <# .Synopsis Get Active Directory user thumbnail image .Description Extracts the current user thumbnail image from active directory .Parameter UserName The username of the person you're looking for .Parameter Path The path for the file you would like to output to, i.e. c:\test.jpg .Example Get-ADThumbnailPhoto -UserName PeterRossi -Path c:\PeterRossi.JPG This will extract the thumbnail image for user PeterRossi and export it to c:\PeterRossi.jpg .Notes Name: Get-ADThumbnailPhoto Author: Peter Rossi Last Edited: 13th May 2011 #> param( [Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$False)] [string[]]$UserName, [Parameter(Mandatory=$True, ValueFromPipeline=$false)] [String]$Path ) $ADSearcher = new-object DirectoryServices.DirectorySearcher("(&(SAMAccountName=$UserName))") $Users = $ADSearcher.FindOne() if($Users -ne $null) { [adsi]$TheUser = "$($Users.Path)" $Thumbnail = $TheUser.ThumbnailPhoto.Value [System.IO.File]::WriteAllBytes($Path,$Thumbnail) } Else { Write-Warning "User $UserName could not be found in AD, is it the right username?" } } Function Set-ADThumbnailPhoto() { <# .Synopsis Set Active Directory user thumbnail image .Description Sets the current user thumbnail image from active directory .Parameter UserName The username of the person you're looking for .Parameter JPGPath The path for the file you would like to use as the new thumbnail .Example Set-ADThumbnailPhoto -UserName PeterRossi -JPGPath c:\PeterRossi.JPG This will set the thumbnail image for user PeterRossi using c:\PeterRossi.jpg .Notes Name: Set-ADThumbnailPhoto Author: Peter Rossi Last Edited: 13th May 2011 #> param( [Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$False)] [string[]]$UserName, [Parameter(Mandatory=$True, ValueFromPipeline=$false)] [String]$JPGPath ) if((Test-Path $JPGPath) -eq $true) { [byte[]]$Thumbnail = Get-Content $JPGPath -encoding byte $ADSearcher = new-object DirectoryServices.DirectorySearcher("(&(SAMAccountName=$UserName))") $UserSearch = $ADSearcher.FindOne() if($UserSearch -ne $null) { $User = [ADSI]"$($UserSearch.Path)" $User.put("thumbnailPhoto", $Thumbnail ) $User.setinfo() } Else { Write-Warning "User $UserName could not be found in AD, is it the right username?" } } Else { Write-Warning "Can't find $JPGPath, does it exist?" } }
Creating a Throttle for Background Tasks Without Rewriting Your Code – V2
A while ago I posted some code which allows you to add a function called Threshold to your script to throttle the number of background tasks a loop can run at any one time.
The idea behind this was that if you are looping through a dataset and spawning a thread for each item, you may well run out of memory or hit some sort of other limit which might break your script, or return incorrect data.
This function allows you have the best of both worlds: The consistency of serial running, with the benefits of background tasks.
NOTE - If you're using Invoke-Command you can always use the -ThrottleLimit switch, although, this only works if you are passing in a parameter which contains a list of computers to run against.
In your loop, before you call a "Start-Job" or an "Invoke-Command" all you need to add is:
Limit-Jobs -MaxConcurrent 10 -PauseTime 10
This will call out to the Limit-Jobs function, which will:
- Check the number of currently running background jobs
- If the amount of currently running jobs is greater than or equal to the parameter passed in for MaxConcurrent it will wait for the number of seconds passed in under PauseTime. It will then check again.
- If the amount of currently running jobs is less than the parmeter passed into MaxConcurrent it will allow your script to continue.
Therefore, the above command will allow 10 concurrent jobs to run at a time and add a pause of 10 seconds in before checking again.
Function Limit-Jobs { Param([int]$MaxConcurrent,[int]$PauseTime) $jobs = (get-job -state running | Measure-Object).count $RunningJobs = 0 if($jobs -ne $null) {$RunningJobs = $jobs} while($RunningJobs -ge $MaxConcurrent) { $jobs = (get-job -state running | Measure-Object).count $RunningJobs = 0 if($jobs -ne $null){ $RunningJobs = $jobs } Write-Warning "Current Running Jobs: $RunningJobs" start-sleep -seconds $PauseTime } }
Object Property Expansion in Strings
Just a quick on really. Ever had a problem where you had a property of an object that was a string that you wanted to use in another string?
For example, if you had a variable that looked like this:
$Name = "Posh Pete"
You could expand that really easily in another string:
write-host "Hi, my name is $Name"
Which returns "Hi, my name is Posh Pete" which is great.
However, say you had an object that had 2 properties, "FirstName" and "LastName"
#Quick and dirty object creation $NameObject = "" | Select FirstName,LastName $NameObject.FirstName = "Posh" $NameObject.LastName = "Pete"
Now, say you wanted to do what we did before:
write-host "Hi, my name is $NameObject.FirstName $NameObject.LastName"
You get a rather nasty looking response:
Hi, my name is @{FirstName=Posh; LastName=Pete}.FirstName @{FirstName=Posh; LastName=Pete}.LastName
Eeeeek!
So, to get around this, all you need to do is wrap your expressions in a $(), so it looks like this:
write-host "Hi, my name is $($NameObject.FirstName) $($NameObject.LastName)"
Job done
Don Jones Powershell Challenge – My Answers
This is my response to Don Jones Powershell Challenge posted on 9th March 2011.
The challenge can be found here
#Problem 1 Function Change-DataServerPassword() { <# .Synopsis Change the password for the service account on servers in a given OU .Description Changes the password used for a service on all computers in a given OU. By default this is the Database Servers OU, and the DataServer service. .Parameter OUName The name of the OU where the servers exist .Parameter ServiceName The name of the service you want to target .Parameter UserName The username for the account you want to use for the service (optional) .Parameter Password The password you want to set the service account to. .Example Change-DataServerPassword -Password MyNewPassword This will set the DataServer account password on all servers in the Database Servers OU. .Example Change-DataServerPassword -OUName AnotherOU -Password MyNewPassword This will set the DataServer account password on all servers in the AnotherOU OU. .Example Change-DataServerPassword -OUName AnotherOU -ServiceName AnotherService -Password MyNewPassword This will set the AnotherService account password on all servers in the AnotherOU OU. .Example Change-DataServerPassword -OUName AnotherOU -ServiceName AnotherService -UserName AnotherUserName -Password MyNewPassword This will set the AnotherService account password for the AnotherUserName account on all servers in the AnotherOU OU. .Notes Name: Change-DataServerPassword Author: Peter Rossi Last Edited: 9th March 2011 #> Param ( [parameter(Mandatory=$true,ValueFromPipeline=$False)][String[]]$OUName="Database Servers", [parameter(Mandatory=$true,ValueFromPipeline=$False)][String[]]$ServiceName="DataServer", [parameter(Mandatory=$false,ValueFromPipeline=$False)][String[]]$UserName, [parameter(Mandatory=$true,ValueFromPipeline=$False)][String[]]$Password ) $ADSearcher = new-object DirectoryServices.DirectorySearcher("(&(objectClass=organizationalUnit)(ou=$OUName))") $OUs = $ADSearcher.FindAll() #Assuming there is only one of these OU's $ThisOU = [ADSI]$OUs[0].Path ForEach($Child in $ThisOU.psbase.children) { if($Child.ObjectCategory -like '*computer*') { $svc=gwmi win32_service -filter "name='$ServiceName'" -ComputerName $Child.Name $inParams = $svc.psbase.getMethodParameters("Change") $inParams["StartName"] = $UserName $inParams["StartPassword"] = $Password $svc.invokeMethod("Change",$inParams,$null) $Message = "Changing Passowrd for $ServiceName on " + $Child.Name Write-Host $Message } } } #Problem 2 Function Get-DiskInventory() { <# .Synopsis Returns a report of all logical drives in a given list of servers where their disk space is below the threshold set by DiskSpaceRemaining .Description Queries a list of remote servers to return a disk space report for their logical drives. .Parameter ServerListFile The path to the text file with server names .Parameter DiskSpaceRemainingGB The amount of disk space remaining that you are concerned about. .Example Get-DiskInventory -ServerListFile c:\servers.txt This will return a report of all logical drives for the servers listed in c:\servers.txt .Example Get-DiskInventory -ServerListFile c:\servers.txt -DiskSpaceRemainingGB 10 This will return a report of all logical drives for the servers listed in c:\servers.txt where there is less than 10GB remaining. .Notes Name: Get-DiskInventory Author: Peter Rossi Last Edited: 9th March 2011 #> Param ( [parameter(Mandatory=$true,ValueFromPipeline=$False)][String[]]$ServerListFile, [parameter(Mandatory=$true,ValueFromPipeline=$False)][Int[]]$DiskSpaceRemainingGB=20 ) if((Test-Path $ServerListFile) -eq $False) { Write-Host "The Server List File Doesn't Exist" Return $null } Else { $DiskSpaceInKB = $DiskSpaceRemainingGB * 1024 * 1024 $Servers = Get-Content $ServerListFile $FinalData = @() ForEach($Server in $Servers) { $Disks = gwmi win32_logicaldisk -Filter "DriveType=3 and FreeSpace <= $DiskSpaceInKB" ForEach($Disk in $Disks) { $ARow = "" | Select Server,DriveLetter,FreeSpaceGB,TotalSizeGB,PercentFreeSpace $ARow.Server = $Server $ARow.DriveLetter = $Disk.DeviceID $ARow.FreeSpaceGB = $Disk.FreeSpace/1024/1024 $ARow.TotalSizeGB = $Disk.Size/1024/1024 $ARow.PercentFreeSpace = (($Disk.FreeSpace/$Disk.Size)*100) $FinalData += $ARow } } Return $FinalData } } #Problem 3 Function Get-ADComputerInfo { <# .Synopsis Returns a report of all computers in AD .Description Queries AD for all computers and returns their Windows Version, Service Pack Version and BIOS serial number .Parameter WindowsVersion The windows version you want to filter by (optional) .Parameter ServicePackVersion The Service Pack Version you want to filter by (optional) .Example Get-ADComputerInfo This will return a report of all servers in AD .Notes Name: Get-ADComputerInfo Author: Peter Rossi Last Edited: 9th March 2011 #> Param ( [parameter(Mandatory=$False,ValueFromPipeline=$False)][String[]]$WindowsVersion, [parameter(Mandatory=$False,ValueFromPipeline=$False)][String[]]$ServicePackVersion ) $ADSearcher = new-object DirectoryServices.DirectorySearcher("(&(objectClass=computer))") $Servers = $ADSearcher.FindAll() $FinalTable = @() ForEach($ServerObj in $Servers) { $Server = [ADSI]$ServerObj.Path $ARow = "" | Select Server,WindowsVersionNumber,BIOSSerial,ServicePackVersion $BiosInfo = gwmi win32_bios -ComputerName $Server.Name $ARow.WindowsVersionNumber = $Server.operatingSystemVersion $ARow.BIOSSerial = $BiosInfo.SerialNumber $ARow.ServicePackVersion = $Server.operatingSystemServicePack $OkToAdd = $True if($WindowsVersion) { if($ARow.WindowsVersionNumber -eq $WindowsVersion) { $OkToAdd = $True } Else { $OkToAdd = $False } } If($ServicePackVersion) { if($ARow.ServicePackVersion -eq $ServicePackVersion) { $OkToAdd = $True } Else { $OkToAdd = $False } } if($OkToAdd -eq $true) { $FinalTable += $ARow } } Return $FinalTable } #Problem 4 Function Add-Users() { <# .Synopsis Creates AD users for all users in a given CSV and adds the to the Employees group .Description Takes a CSV file of new users, creates them in AD and adds them to the Employees group. .Parameter ImportCSV The path to the CSV file with the users .Example Add-Users -ImportCSV c:\Users.csv This will import all users in c:\users.csv into AD .Notes Name: Add-Users Author: Peter Rossi Last Edited: 9th March 2011 #> Param ( [parameter(Mandatory=$True,ValueFromPipeline=$False)][String[]]$ImportCSV ) if((Test-Path $ImportCSV) -eq $false) { Write-Host "The file $ImportCSV doesn't exist" return $null } Else { $ImportData = Import-Csv -Path $ImportCSV $ADSearcher = new-object DirectoryServices.DirectorySearcher("(&(objectClass=group)(name=Employees))") $Groups = $ADSearcher.FindAll() $Group = [ADSI]$Groups[0].Path $GroupMembers = $Group.Members $OUSearcher = new-object DirectoryServices.DirectorySearcher("(&(objectClass=organizationalUnit)(ou=Users))") $UsersOUs = $OUSearcher.FindAll() $UserOuObj = $UsersOUs[0].Path ForEach($User in $ImportData) { $Message = "Adding User " + $User.UserName Write-Host $Message $usersOU = [adsi]$UserOuObj $UserName = $User.UserName $Department = $User.Department $Title = $User.Title $City = $User.City $newUser = $usersOU.Create("user","cn=$UserName") $newUser.put("title", $Title) $newUser.put("department", $Department) $newUser.put("physicalDeliveryOfficeName",$City) $newUser.SetInfo() $Group.Members = $GroupMembers + $newUser } } }



