PowerShell - Enumerating through a collection and change the collection - sharepoint

How it is posible to fix this script?
Yes, I´m changing the collection in the foreach loop and this is the reason for this error.
An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute..
At C:\Users\user\Documents\PowerShell\ChangeAllListsV2.ps1:47 char:20
+ foreach <<<< ($list in $webLists)
+ CategoryInfo : InvalidOperation: (Microsoft.Share...on+SPEnumerator:SPEnumerator) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
#Script change in all lists the required field property "testfield" to false
#Part 0 - Configuration
$urlWebApp = "http://dev.sharepoint.com"
$countFound = 0
$countList = 0
$countFoundAndChange = 0
#Part 1 - PreScript
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq "Microsoft.SharePoint.Powershell"}
if ($snapin -eq $null)
{
Write-Host “Loading SharePoint Powershell”
Add-PSSnapin Microsoft.SharePoint.Powershell
}
#Part 2 - Script
$webApp = Get-SPWebApplication $urlWebApp
#$webApp | fl
$webAppSites = $webApp.sites
foreach($site in $webAppSites)
{
Write-Host "***********************************************************************"
Write-Host "Found site: " $site -foreground blue
$siteAllWebs = $site.AllWebs
foreach($web in $siteAllWebs)
{
Write-Host "Found web: " $web -foreground blue
#$web | fl
$webLists = $web.Lists
foreach($list in $webLists)
{
$countList ++
Write-Host "Found list: " $list -foreground blue
#Change list property
$field = $Null
$field = $list.Fields["testfield"]
if($field){
Write-Host "Field found: " $list -foreground green
#Write-Host "in web: " $web -foreground green
$countFound ++
try{
if($field.Required)
{
#######################################################
$field.Required = $False
$field.Update()
#######################################################
$field = $Null
Write-Host "Done!: Change list: " $list -foreground green
$countFoundAndChange ++
}else{
Write-Host "Already!: Change list: " $list -foreground green
}
}
catch{
$field = $Null
Write-Host "Error!: Change list: " $list -foreground red
Write-Host "in web: " $web -foreground red
$_
}
}
}
}
}
Write-Host "Found lists: " $countList
Write-Host "Found lists with column [testfield]: " $countFound
Write-Host "Change lists with column [testfield]: " $countFoundAndChange

The SPListCollection tends to modify the collection when updating its properties (fields, event receivers, etc.). You can use a for-loop instead:
for ($i = 0; $i -lt $webLists.Count; $i++)
{
$list = $web.Lists[$i];
# ...
}

I know this is a pretty old thread. This is for anybody ending up to this page looking for an answer.
The idea is, like other answers suggest, to copy the collection (using the clone() method) to another and iterate "another" and modify the original variable inside the loop without having to use for in place of foreach:
A collection of type ArrayList:
[System.Collections.ArrayList]$collection1 = "Foo","bar","baz"
$($collection1.Clone()) | foreach {
$collection1.Remove("bar")
}
Output:
PS H:\> $collection1
Foo
baz
A collection of type Hashtable:
[System.Collections.Hashtable]$collection2 = #{
"Forum" = "Stackoverflow"
"Topic" = "PowerShell"
}
$($collection2.Clone())| foreach {
$collection2.Remove("Forum")
}
Output:
PS H:> $collection2
Name Value
---- -----
Topic PowerShell
And, a basic array:
[System.Array]$collection3 = 1, 2, 3, 4
$($collection3.Clone()) | foreach {
$collection3[$collection3.IndexOf($_)] = 10
}
Output:
PS H:\> $collection3
10
10
10
10
As long as your collection is not of fixed size.

You can try copying the collection you're currently iterating on to another collection (an array or a list) and then iterate on that new collection.
Something like this:
$collection = #(1, 2, 3, 4)
$copy = #($collection)
$collection[0] = 10
$collection -join " "
$copy -join " "
The code above gives the following output:
10 2 3 4
1 2 3 4
Note that the $copy variable refers to a different collection.

Check: http://soreddymanjunath.blogspot.in/2014/07/collection-was-modified-enumeration.html
Here is anonther example for same issue
if($web.IsMultilingual -eq $true )
{
foreach($cul in $web.SupportedUICultures)
{
if($cul.LCID -ne $webCul.LCID -and $cul.LCID -ne "1033")
{
$web.RemoveSupportedUICulture($cul)
}
}
$web.Update()
}
for the first time it will go through the loop foreach will remove supported culture for frist time, when it comes to loop for the second iteration then it will throw you the exception “Collection was modified; enumeration operation may not execute”,
Solution to Above problem is to Store to values to modified in a Arraylist and try to modify which will fix the problem, Here i am storing Arraylist called enumcul and inserting values into it and modifying it...
$enumcul=New-Object Collections.ArrayList
$i=0
if($web.IsMultilingual -eq $true )
{
foreach($cul in $web.SupportedUICultures)
{
if($cul.LCID -ne $webCul.LCID -and $cul.LCID -ne "1033")
{
$enumcul.Insert($i, $cul)
$i=$i+1
}
}
foreach( $k in $enumcul)
{
$web.RemoveSupportedUICulture($k)
$web.Update()
}

Related

Two objects of different sizes and $null

My code below works in every instance except for if one object is $null and the other object has one item. When that situation occurs the output becomes 1 letter like it is indexing and I am not sure why.
How do I combine the two objects to make a final report?
$ADGroups = Get-ADPrincipalGroupMembership -Identity $UserSam | Select-Object distinguishedName, name | Where-Object { ($_.distinguishedName -ne 'CN=Domain Users,CN=Users,DC=com') }
#record AD groups
$ADResult = #()
if ($null -eq $ADGroups) {
Write-Warning "No AD Groups"
$ADResult = [PSCustomObject]#{
ADGroups = #()
ADGroupsdistinguishedName = #()
}
}
Else {
$ADResult = $ADGroups | ForEach-Object {
[PSCustomObject]#{
ADGroups = $_.name
ADGroupsdistinguishedName = $_.distinguishedName
}
}
}
#============= Now Google, get user groups and record
$GoogleGroups = gam print groups member $email members managers owners | ConvertFrom-Csv
# Record Google Groups
$GResult = #()
If ($null -eq $GoogleGroups) {
Write-Warning "No Google Groups"
$GResult = [PSCustomObject]#{
GoogleGroups = #()
Role = #()
}
}
Else {
$group = $null
$GResult = ForEach ($group in $GoogleGroups) {
#this records what role the user had in the group(s)
$GoogleMember = gam print group-members group $group.email members | ConvertFrom-Csv | Select-Object -ExpandProperty email
$Role = $null
If ( $GoogleMember -contains $EMAIL) {
$Role = 'Member'
}
Else {
$GoogleManager = gam print group-members group $group.email managers | ConvertFrom-Csv | Select-Object -ExpandProperty email
If ($GoogleManager -contains $EMAIL) {
$Role = 'Manager'
}
Else {
$Role = 'Owner'
}
}
[PSCustomObject]#{
GoogleGroups = $group.email
Role = $role
}
$group = $null
}
}
# ---------now report that will be dropped off at end
[int]$max = $ADResult.count
if ([int]$GResult.count -gt $max) { [int]$max = $GResult.count }
If ($max -eq 1 -or $max -eq 0) {
$Result = [PSCustomObject]#{
PrimaryEmail = $email
Title = $UserInfo.title
Department = $UserInfo.Department
Manager = $Manager
ADGroupName = $ADResult.ADGroups
ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName
GoogleGroup = $GResult.GoogleGroups
Role = $GResult.role
DateOfSeparation = (Get-Date).ToString("yyyy_MM_dd")
UserDistinguishedName = $UserInfo.distinguishedName
UserOU = $UserInfo.Ou
PrimaryGroup = $UserInfo.primaryGroup.Split('=').Split(',')
}
}
Else {
$Result = for ( $i = 0; $i -lt $max; $i++) {
[PSCustomObject]#{
PrimaryEmail = $email
Title = $UserInfo.title
Department = $UserInfo.Department
ADGroupName = $ADResult.ADGroups[$i]
ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName[$i]
GoogleGroup = $GResult.GoogleGroups[$i]
Role = $GResult.role[$i]
DateOfSeparation = (Get-Date).ToString("yyyy_MM_dd")
UserDistinguishedName = $UserInfo.distinguishedName
UserOU = $UserInfo.Ou
PrimaryGroup = $UserInfo.primaryGroup.Split('=').Split(',')[$i]
}
}
}
$Result | Export-Csv 'C:\temp\Groups.csv' -NoTypeInformation
Going by the abstract description of your problem:
You're seeing an unfortunate asymmetry in PowerShell:
In the pipeline, a [string] instance is considered a single object.
PS> ('foo' | Measure-Object).Count
1
With respect to indexing, it is considered an array of characters.
PS> 'foo'[0]
f
A general feature of capturing a PowerShell pipeline's output is that if a command situationally outputs just a single object, that object is captured as-is, whereas two or more output objects result in a regular PowerShell array, of type [object[]].
Typically, this isn't a problem, because PowerShell's unified handling of scalars and collections allows you to index even into a scalar (single object), i.e. to implicitly treat a single object as if it were a single-element array:
PS> (Write-Output 42, 43)[0]
42
PS> (Write-Output 42)[0]
42 # still OK, even though only *one* object was output; same as: (42)[0]
However, with a single [string] instance as the output it becomes a problem, for the reasons stated above:
PS> (Write-Output 'foo', 'bar')[0]
foo # OK
PS> (Write-Output 'foo')[0]
f # !! Indexing into a *single string* treats it as *character array*
The same applies to values returned via member-access enumeration, perhaps surprisingly :
PS> (Get-Item $HOME, /).FullName[0]
C:\Users\Jdoe
PS> (Get-Item $HOME).FullName[0]
C # !! Indexing into a *single string* treats it as *character array*
Workarounds:
Enclose the command of interest in #(...), the array-subexpression operator so as to ensure that its output is always considered an array.
PS> #(Write-Output 'foo')[0]
foo # OK
Alternatively, when capturing a command's output in a variable, type-constrain that variable to [array] (same as [object[]]) or a strongly typed array, [string[]]:
PS> [array] $output = Write-Output 'foo'; $output[0]
foo # OK

PowerShell: Working with Strings and Hashtables

I have this code:
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
Write-Host "set $pass ="$passwordsTable[$pass]""
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
# Add-Content "d:\myFile.txt" "set $pass ="$passwordsTable[$pass]""
Which perfectly works when I use Write-Host. But I want to do something like in the comment in line 25. I tried several ways but I always got a static string instead of the values that I get from the Hashtable.
At the end I want to have something like:
set pass1 = 12
set pass2 = 5
in myFile.txt
Any help is appreciated. Thanks!
You could change Write-Host (just prints to a console) to Write-Output ( which passes an object to a pipeline). Write-Output does not print to the console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
$counter=0
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
$counter++
Write-Output "set "pass$counter = $passwordsTable[$pass]"`n" | Add-Content -NoNewline myFile.txt
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
Output:
set pass1=10
set pass2=13
set pass3=12
You can replace the first foreach loop if you simply replace all the &&& by a newline and use cmdlet ConvertFrom-StringData.
Add-Content also has a switch called -PassThru that will let you write to the file and also output to console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$passwordsTable = $passwordsParameters -replace '&&&', [Environment]::NewLine | ConvertFrom-StringData
foreach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass]) {
$msg = 'set {0} = {1}' -f $pass, $passwordsTable[$pass]
# or use: $msg = "set $pass = $($passwordsTable[$pass])"
# write this to the file. switch -PassThru will also output to the console
Add-Content -Path 'D:\myFile.txt' -Value $msg -PassThru
} else {
Write-Host "A value for the parameter '$pass' was not entered."
}
}

Unable to create two-way table in excel from powershell

I want to create two way table in excel by exporting object from powershell. I am able to create a table in powershell.
The code as shown below:
class sampleClass {
[String] $var1
[String] $var2
[Bool] $boolVar
sampleClass([String] $var1, [String] $var2, [Bool] $boolVar)
{
$this.var1 = $var1
$this.var2 = $var2
$this.boolVar = $boolVar
}
[String] ToString()
{
return $this.var1 + ": " + $this.var2 + ": " + $this.boolVar
}
}
$s1 = [sampleClass]::new("Comp1", "S1", $false)
$s2 = [sampleClass]::new("Comp2", "S2", $true)
$s3 = [sampleClass]::new("Comp1", "S2", $false)
$s4 = [sampleClass]::new("Comp2", "S1", $false)
$s = #()
$s += $s1
$s += $s2
$s += $s3
$s += $s4
$s | Export-Csv .\out.csv -NoTypeInformation
The output for above code is as shown below:
But the output that I want is not that, but as shown below:
Kindly help.
This code may be your best bet:
function Transpose-Object
{ [CmdletBinding()]
Param([OBJECT][Parameter(ValueFromPipeline = $TRUE)]$InputObject)
BEGIN
{ # initialize variables just to be "clean"
$Props = #()
$PropNames = #()
$InstanceNames = #()
}
PROCESS
{
if ($Props.Length -eq 0)
{ # when first object in pipeline arrives retrieve its property names
$PropNames = $InputObject.PSObject.Properties | Select-Object -ExpandProperty Name
# and create a PSCustomobject in an array for each property
$InputObject.PSObject.Properties | %{ $Props += New-Object -TypeName PSObject -Property #{Property = $_.Name} }
}
if ($InputObject.Name)
{ # does object have a "Name" property?
$Property = $InputObject.Name
} else { # no, take object itself as property name
$Property = $InputObject | Out-String
}
if ($InstanceNames -contains $Property)
{ # does multiple occurence of name exist?
$COUNTER = 0
do { # yes, append a number in brackets to name
$COUNTER++
$Property = "$($InputObject.Name) ({0})" -f $COUNTER
} while ($InstanceNames -contains $Property)
}
# add current name to name list for next name check
$InstanceNames += $Property
# retrieve property values and add them to the property's PSCustomobject
$COUNTER = 0
$PropNames | %{
if ($InputObject.($_))
{ # property exists for current object
$Props[$COUNTER] | Add-Member -Name $Property -Type NoteProperty -Value $InputObject.($_)
} else { # property does not exist for current object, add $NULL value
$Props[$COUNTER] | Add-Member -Name $Property -Type NoteProperty -Value $NULL
}
$COUNTER++
}
}
END
{
# return collection of PSCustomobjects with property values
$Props
}
}
It will allow you to turn you columns into rows and then export the object. Use like this:
$s | Transpose-Object | Export-Csv .\out.csv -NoTypeInformation

Efficiently search a string in large files

How can I check if a string exists in:
1 text file;
size up until 10GB;
taking into account that the file is only one line;
the file only contains random numbers 1 to 9;
using powershell (because I think it will be more efficient, although I don't know how to program in this language);
I have tried this in batch:
FINDSTR "897516" decimal_output.txt
pause
But as I said I need the faster and more efficient way to do this.
I also tried this code that I have found in stackoverflow:
$SEL = Select-String -Path C:\Users\fabio\Desktop\CONVERTIDOS\dec_output.txt -Pattern "123456"
if ($SEL -ne $null)
{
echo Contains String
}
else
{
echo Not Contains String
}
But I get the error below, and I don't know if this code is the most solid or adequate. The error:
Select-String : Tipo de excepção 'System.OutOfMemoryException' accionado.
At C:\Users\fabio\Desktop\1.ps1:1 char:8
+ $SEL = Select-String -Path C:\Users\fabio\Desktop\CONVERTIDOS\dec_out ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Select-String], OutOfMemoryException
+ FullyQualifiedErrorId : System.OutOfMemoryException,Microsoft.PowerShell.Commands.SelectStringCommand
This should do the job:
#################################################################################################################
#
# Searches for a user defined string in the $input_file and counts matches. Works with files of any size.
#
# Adjust source directory and input file name.
#
$source = "C:\adjust\path"
$input_file = "file_name.extension"
#
#
# Define the string you want to search for. Keep quotation marks even if you only search for numbers (otherwise
# $pattern.Length will be 1 and this script will no longer work with files larger than the $split_size)!
#
$pattern = "Enter the string to search for in here"
#
#
# Using Get-Content on an input file with a size of 1GB or more will cause System.OutOfMemoryExceptions,
# therefore a large file gets temporarily split up.
#
$split_size = 100MB
#
#
# Thanks #Bob (https://superuser.com/a/1295082/868077)
#################################################################################################################
Set-Location $source
if (test-path ".\_split") {
while ($overwrite -ne "true" -and $overwrite -ne "false") {
"`n"
$overwrite = Read-Host ' Splitted files already/still exist! Delete and overwrite?'
if ($overwrite -match "y") {
$overwrite = "true"
Remove-Item .\_split -force -recurse
$a = "`n Deleted existing splitted files!"
} elseif ($overwrite -match "n") {
$overwrite = "false"
$a = "`n Continuing with existing splitted files!"
} elseif ($overwrite -match "c") {
exit
} else {
Write-Host "`n Error: Invalid input!`n Type 'y' for 'yes'. Type 'n' for 'no'. Type 'c' for 'cancel'. `n`n`n"
}
}
}
Clear-Host
if ((Get-Item $input_file).Length -gt $split_size) {
while ($delete -ne "true" -and $delete -ne "false") {
"`n"
$delete = Read-Host ' Delete splitted files afterwards?'
if ($delete -match "y") {
$delete = "true"
$b = "`n Splitted files will be deleted afterwards!"
} elseif ($delete -match "n") {
$delete = "false"
$b = "`n Splitted files will not be deleted afterwards!"
} elseif ($delete -match "c") {
exit
} else {
Write-Host "`n Error: Invalid input!`n Type 'y' for 'yes'. Type 'n' for 'no'. Type 'c' for 'cancel'. `n`n`n"
}
}
Clear-Host
$a
$b
Write-Host `n This may take some time!
if ($overwrite -ne "false") {
New-Item -ItemType directory -Path ".\_split" >$null 2>&1
[Environment]::CurrentDirectory = Get-Location
$bytes = New-Object byte[] 4096
$in_file = [System.IO.File]::OpenRead($input_file)
$file_count = 0
$finished = $false
while (!$finished) {
$file_count++
$bytes_to_read = $split_size
$out_file = New-Object System.IO.FileStream ".\_split\_split_$file_count.splt",CreateNew,Write,None
while ($bytes_to_read) {
$bytes_read = $in_file.Read($bytes, 0, [Math]::Min($bytes.Length, $bytes_to_read))
if (!$bytes_read) {
$finished = $true
break
}
$bytes_to_read -= $bytes_read
$out_file.Write($bytes, 0, $bytes_read)
}
$out_file.Dispose()
}
$in_file.Dispose()
}
$i++
while (Test-Path ".\_split\_split_$i.splt") {
$cur_file = (Get-Content ".\_split\_split_$i.splt")
$temp_count = ([regex]::Matches($cur_file, "$pattern")).Count
$match_count += $temp_count
$n = $i - 1
if (Test-Path ".\_split\_split_$n.splt") {
if ($cur_file.Length -ge $pattern.Length) {
$file_transition = $prev_file.Substring($prev_file.Length - ($pattern.Length - 1)) + $cur_file.Substring(0,($pattern.Length - 1))
} else {
$file_transition = $prev_file.Substring($prev_file.Length - ($pattern.Length - 1)) + $cur_file
}
$temp_count = ([regex]::Matches($file_transition, "$pattern")).Count
$match_count += $temp_count
}
$prev_file = $cur_file
$i++
}
} else {
$a
$match_count = ([regex]::Matches($input_file, "$pattern")).Count
}
if ($delete -eq "true") {
Remove-Item ".\_split" -Force -Recurse
}
if ($match_count -ge 1) {
Write-Host "`n`n String '$pattern' found:`n`n $match_count matches!"
} else {
Write-Host "`n`n String '$pattern' not found!"
}
Write-Host `n`n`n`n`n
Pause
This will split a large file into mutliple smaller files, search them for $pattern and count the matches (taking file transitions into account).
It also offers you to delete or keep the splitted files afterwards so you can reuse them and don't have to split the large file every time you run this script.

Active Directory Filter memberof

I am trying to get all of the CN's out of active directory in order to populate groups based on that name into Sharepoint Services. I can list the "memberof" section but I can not seem to split it using split(",")
$Dom = 'LDAP://OU=External,OU=Users,OU=HomeOffice,DC=mydoman,DC=com'
$Root = New-Object DirectoryServices.DirectoryEntry $Dom
$i=0
# Create a selector and start searching from the Root of AD
$selector = New-Object DirectoryServices.DirectorySearcher
$selector.SearchRoot = $root
$adobj= $selector.findall() |`
where {$_.properties.objectcategory -match "CN=Person"}
foreach ($person in $adobj){
$prop=$person.properties
$i++
Write-host "$($prop.department) - $($prop.sn), $($prop.givenname)"
Write-host $person.properties["memberof"]
}
"Total $i"
Now I get everything I need, but I need some way to filter only the CN's out...
As a general rule, write-host is not the best way to generate output. Ideally, you want to emit objects out of your function and let PowerShell do the formatting for you. This is the more "pipeline friendly" way of doing things. In this case, if you had a function Get-GroupMembers you could pipe it to something like
Get-Person | ft CN
The trick is creating a new object and adding properties to it, or just emitting the DirectoryServices object you are pulling already. To create a new custom object you can do the following:
$obj = new-object psobject
$obj | add-member -membertype noteproperty name $PropName -value $valueToStore
People can use your function and pipe it to format-table, format-list, select-object, group-object, sort-object and a variety of other things. Keith Hill's Effective PowerShell has a great chapter on Output that you might find helpful.
There is also an article by Don Jones on using objects instead of text that is quite good as well.
test1.ps1
#Connet using LDAP
$Dom = 'LDAP://OU=External Accounts,OU=Users,OU=The Office,DC=mydomain,DC=com'
$Root = New-Object DirectoryServices.DirectoryEntry $Dom
#Integer for the loop
$i=0
# Create a selector and start searching from the Root of AD
$selector = New-Object DirectoryServices.DirectorySearcher
$selector.SearchRoot = $root
#Find the Groups
$adobj= $selector.findall() |`
where {$_.properties.objectcategory -match "CN=Person"}
foreach ($person in $adobj){
$prop=$person.properties
$i++
#Write-host "$($prop.department) - $($prop.sn), $($prop.givenname)" -foregroundcolor Magenta
$test = $person.properties["memberof"]
ForEach-Object {
$test`
-replace "CN=OLDLEGACYGROUP",""`
-replace "CN=",""`
-replace ",OU=Sales",""`
-replace ",OU=Some Groups",""`
-replace ",OU=Groups","" `
-replace ",OU=The Office","" `
-replace ",DC=mydomain","" `
-replace ",DC=com","" `
-replace ",","`r`n"
}
}
test2.ps1
# Lets start with a clean slate :)
Clear
# Lets reference the assembly / GAC that we need for this
#region
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$SPSite = New-Object Microsoft.SharePoint.SPSite("https://myintranetorextranetsite.myfqdn.com")
$OpenWeb = $SpSite.OpenWeb("/")
#endregion
# Add some eye candy :)
# region
# I really wanted some old school thing in here :)
write-host " _ ____ ____ " -foregroundcolor Magenta
write-host " / \ | _ \ / ___| _ _ _ __ ___ " -foregroundcolor Magenta
write-host " / _ \ | | | |____\___ \| | | | '_ \ / __|" -foregroundcolor Magenta
write-host " / ___ \| |_| |_____|__) | |_| | | | | (__ " -foregroundcolor Magenta
write-host "|_/ \_\____/ |____/ \__, |_| |_|\___|" -foregroundcolor Magenta
write-host " |___/ " -foregroundcolor Magenta
Write-Host " Version 2.0" -foregroundcolor Red
Write-Host " Build 2009 09-11 21:30" -foregroundcolor Red
Write-host " Created by Mitchell J. Skurnik" -foregroundcolor Red
#endregion
# Create the stopwatch
#region
[System.Diagnostics.Stopwatch] $sw;
$sw = New-Object System.Diagnostics.StopWatch
$sw.Stop()
$sw.Start()
#endregion
# Function to control Adding groups
function creategroup
{
param ([string] $siteurl = "https://myintranetorextranetsite.myfqdn.com")
$site = New-Object Microsoft.SharePoint.SPSite($siteurl)
$web = $site.RootWeb;
$group = $currentgroup;
$perm = "Read";
$owner = "jdoe";
if ($owner -eq "") { $owner = $web.CurrentUser.LoginName }
$exists = $web.SiteGroups | where { $_.Name -eq $group }
if ($exists -eq $null)
{
# Create group
$web.SiteGroups.Add($group, $web.EnsureUser($owner), $null, "");
# Give permissions to the group
$assign = New-Object Microsoft.SharePoint.SPRoleAssignment($web.SiteGroups[$group]);
$assign.RoleDefinitionBindings.Add($web.RoleDefinitions[$perm])
$web.RoleAssignments.Add($assign)
Write-Host -ForegroundColor green "Creating sharepoint group - " $currentgroup;
}
$site.Dispose();
}
# Function to add users to the specified group
function addUser
{
# Open a connection to the sharepoint site and then select the sub site you want
$themail = $prop.mail
$thedisplay = $prop.displayname
# If there are accounts that dont have some info lets populate it
if ($themail -eq "")
{
$themail = "testaccount#myfqdn.com"
}
if ($thedisplay -eq "")
{
$thedisplay = "Account, Test"
}
if ($themail -eq $null)
{
$themail = "testaccount#myfqdn.com"
}
if ($thedisplay -eq $null)
{
$thedisplay = "Account, Test"
}
$TheNewGroup = $OpenWeb.SiteGroups | Where-Object {$_.Name -match $currentGroup}
$TheNewGroup.AddUser("NTAMR\" + $prop.samaccountname,$themail,$prop.displayname,"")
#write-host "Added: " $thedisplay -foregroundcolor Red
}
# Function to remove people - be careful using this script :(
# Also not done
function removeUser
{
#$TheNewGroup = $OpenWeb.SiteGroups | Where-Object {$_.Name -match $currentGroup}
#$TheNewGroup.AddUser("NTAMR\" + $prop.samaccountname,$themail,$prop.displayname,"")
#$TheNewGroup.Remove($LoginToDel)
}
# Now onto the real stuff
Write-host "Searching for Groups" -foregroundcolor Green
# Clear out the existing text file so we have a clean slate
$file = New-Item -type file "C:\location\to\my\folder\allGroups.txt" -Force
# Execute the Group Dump Script
C:\location\to\my\folder\test.ps1 | Out-File -filepath "C:\location\to\my\folder\allGroups.txt" -append
# Clean up the list by removing duplicates and sorting everything
$TextFile = $TextFile = "C:\Powershell\allGroups.txt"
$NewTextFile = "C:\Powershell\allGroups - Sorted.txt"
GC $TextFile | Sort | GU > $NewTextFile
# Use LDAP to connect to Active Directory
#region
$Dom = 'LDAP://OU=External Accounts,OU=Users,OU=The Office,DC=mydomain,DC=com'
$Root = New-Object DirectoryServices.DirectoryEntry $Dom
#endregion
# Create a selector and start searching from the Root of AD
#region
$selector = New-Object DirectoryServices.DirectorySearcher
$selector.SearchRoot = $root
#endregion
# Integer to compare file length
$c=0
# Get the Group text file's length and write to scree and variable
$fileLength = [System.IO.File]::ReadAllText($NewTextFile).Split("`n").Count
Write-Host "Found " $fileLength "Groups in Active Directory" -foregroundcolor Magenta
# Integer for thumbing through 'memberOf' in active directory
$d = 0
# Integer for the amount of of users found
$f = 0
# Start a while loop where we read through the entire groups text file
while ($c -le $fileLength)
{
# Increment the line number for the next pass through
$c++
# Grab the first line of text from the groups file (Really the 0th line) and then tell the user
$currentGroup = (Get-Content $NewTextFile)[$c]
# Create the group
CreateGroup
#Write-Host "Created Group: " $currentGroup -foregroundcolor Red
#
Write-host $c "/" $fileLength "`t" $currentGroup -foregroundcolor Red
# Query Active directory and force some commands
$adobj= $selector.findall() | where {$_.properties.objectcategory -match "CN=Person"}
foreach ($person in $adobj)
{
# Variable for the different properties to reduce fatigue
$prop=$person.properties
# The Department
$department = $prop.department
# Sir Name
$sn = $prop.sn
# Given Name
$gn = $prop.givenname
$un = $prop.samaccountname
# Assign the really long memberof to a variable
$memberof = $person.properties["memberof"]
# Length of memberof
$memberofcount = $test.Count
# Loop for each group the member is in
while ($d -le $memberof.Count)
{
$blah = ForEach-Object{`
$memberof[$d]`
-replace "CN=OLDLEGACYGROUP",""`
-replace "CN=",""`
-replace ",OU=Sales",""`
-replace ",OU=Some Groups",""`
-replace ",OU=Groups","" `
-replace ",OU=The Office","" `
-replace ",DC=mydomain","" `
-replace ",DC=com","" `
}
# Incriment the d
$d++
# Is that user in the group?
if ($blah -eq $currentGroup)
{
# Hey look we found somebody in that group :)
Write-host "`t`t`t" $un -foregroundcolor Magenta
addUser
$f++
}
#elseif ($blah -ne $currentGroup)
#{
# removeUser
#}
else
{
# Oh noes...nobody is in that group...that is strange
}
}
# Are we at the end of what the user has
if ($d -ge $memberofs.Count)
{
# Looks like we are :)
$d=0
}
}
# Display amount of users found
#Write-Host "`t`t`t" $f " user(s) found"
$f = 0
}
# Stop Watch
$sw.Stop()
# Write the compact output to the screen
write-host "Updated in Time: ", $sw.Elapsed.ToString()
#This space is saved for future development

Resources