netnerds.net

24Oct/110

PowerShell: Copy-Item -recurse -force Does Not Create Directories when Using Get-ChildItem -include

I recently wrote a PowerShell script which copied documents from a filter to my local hard drive. I chose PowerShell for this task because it was one of the few programs actually allowed on my ultra locked-down workstation (and 'cuz I love it.)

Because I only wanted specific types of files, I gathered the list of documents using get-childitem -recurse -include and then copied the files using copy-item. What I didn't initially realize was that copy-item's -recurse and -force switches are ineffective in my script because 1.) I'm passing the copy-item command one item at a time and 2.) the get-childitem include switch is "best considered a file filter," according to Lee Holmes of Microsoft's PowerShell team..

I got around this by manually creating the directory structure as needed using if (!(test-path($dir))) { mkdir $dir }. The entire script can be found below:

$source = "L:\"
$destination = "C:\docs\"
$since = (Get-date).AddDays(-365)
$includes ="*.doc*","*.pdf","*.xls*","*.ppt*"

    $items = get-childitem $source -recurse -include $includes |  where-object {$_.LastAccessTime –gt $since}

    foreach ($item in $items)
    {
               $dir = $item.DirectoryName.Replace($source,$destination)
               $target = $item.FullName.Replace($source,$destination)

                if (!(test-path($dir))) { mkdir $dir }

        if (!(test-path($target)))
        {
            copy-item -path $item.FullName -destination $target -recurse -force
        }
    }
Posted by: Chrissy   Filed under: PowerShell No Comments
20Oct/110

PowerShell: Find/Replace Template

Brandon had this in some of his maintenance plan code and I found myself using it a bit so I thought I'd blog about it. Here is a simple script that reads a file (C:\scripts\maintenanceplans\$sqlversion\template.dtsx), replaces certain placeholders (in this case, [SERVERNAME] and [BACKUPPATH]), then writes a new file with that content.

$serverName = "sqlserver1"
$backupPath = "D:\dbbackups"

# Read in the maintenance plan template and replace the place holders
$textTemplate = [IO.File]::ReadAllText(".\maintenanceplans\$sqlVersion\template.dtsx")
$textTemplate = $textTemplate.Replace("[SERVERNAME]","$serverName")
$textTemplate = $textTemplate.Replace("[BACKUPPATH]","$backupPath")

# Write the new package.  MUST use UTF8 encoding or it will break.
Set-Content -Encoding utf8 ".\out.dtsx" $packageContents

 
This particular script writes out XML files. If you are writing a plain text file, remove "-Encoding utf8" and you're set.

Posted by: Chrissy   Filed under: PowerShell No Comments
20Sep/110

SharePoint + PowerShell: Batch Import Models, Create ECT’s, Set Permissions

Here is a script that will batch import Models, create associated ECT's, and set permissions for users and admins. Please modify the variables as needed.

# script from netnerds.net
# Make sure the namespace is the same as the namespace in your exported bdcm files
# My $siteURL happens to be the same as my $namespace. That may not be the case for you.
# Do this only on dev machines
# I'm running this on the SharePoint 2010 server so I made my servicecontext localhost
#
# What this does:
# Scans current directory for BDC Export(.bdcm) files
# Creates the External System/Lob and the External Content Types
# Creates External Lists from those ECT's
# Sets the proper permissions

$adminGroup = "DOMAIN\SharePoint Admins"
$userGroup = "DOMAIN\SharePoint Users"
$serviceContext = "http://localhost/"
$nameSpace = "http://sharepoint/"
$SiteUrl = $nameSpace

$bdc = Get-SPBusinessDataCatalogMetadataObject -BdcObjectType Catalog -ServiceContext $serviceContext
$pathtobdcmfiles = Get-Location
$importFiles = Get-Childitem -path $pathtobdcmfiles | Where {$_.extension -eq ".bdcm" -and $_.basename -ne "catalog"}

foreach ($file in $importFiles) {
Import-SPBusinessDataCatalogModel -Path $file.FullName -Identity $bdc -force -ModelsIncluded -PropertiesIncluded -PermissionsIncluded -Verbose

# THIS WHOLE CHUNK COMES FROM: http://gallery.technet.microsoft.com/scriptcenter/82437789-f294-4bcd-8210-a1ba0e081f82/
# IT MAKES THE EXTERNAL LISTS FROM THE MODELS. AWESOME.
    $Model = Get-SPBusinessDataCatalogMetadataObject -BdcObjectType Model -Name $file.baseName -ServiceContext $ServiceContext
    
    $ListUrl = "Lists/{0}"

        ForEach ($Entity in $Model.AllEntities)
        {
            $ns = $Entity.Namespace
            Write-Host "Entity Namespace : $ns"
            $name = $entity.Name
            Write-Host "Entity Name : $name"

            Write-Host "Looking for MethodInstance specific finder..."
            $sf = $Entity.MethodInstances | Where-Object{ $_.MethodInstanceType -eq [Microsoft.BusinessData.MetadataModel.MethodInstanceType]::SpecificFinder }
            If($sf -eq $null)
            {
                Write-Host "Skipping external list creation as Method Instance of Type SpecificFinder was not found in Entity : $name"
                break
            }
            else
            {
                Write-Host "Creating and Configuring SPListDataSource"
                $ds = New-Object -TypeName Microsoft.SharePoint.SPListDataSource
                $instanceName = [String]::Empty
                ForEach($instance in $entity.LobSystem.LobSystemInstances){$instanceName = $Instance.Name}

                $ds.SetProperty("LobSystemInstance", $instanceName)
                $ds.SetProperty("EntityNamespace", $ns)
                $ds.SetProperty("Entity", $name)
                $ds.SetProperty("SpecificFinder", $sf.Name)

                $site = $null
                Try
                {
                    $site = Get-SPSite -Identity $SiteUrl
                    $web = $site.OpenWeb()
                    $web.Lists.Add($Entity.Name, "", [String]::Format($ListUrl, $Entity.Name), $ds);
                }
                Finally
                {
                    $web.Dispose()
                    $site.Dispose()      
                }
            }
        }
#END CHUNK
}

$claimAdmin = New-SPClaimsPrincipal -Identity $adminGroup -IdentityType WindowsSamAccountName
$claimUsers = New-SPClaimsPrincipal -Identity $userGroup -IdentityType WindowsSamAccountName

Grant-SPBusinessDataCatalogMetadataObject -Identity $bdc -Principal $claimAdmin -Right "Execute,SetPermissions,Edit,SelectableInClients"
Grant-SPBusinessDataCatalogMetadataObject -Identity $bdc -Principal $claimUsers -Right "Execute,Edit,SelectableInClients"

Copy-SPBusinessDataCatalogAclToChildren -MetadataObject $bdc

#Ignore this, its what I wish my namespace was...
#$nameSpace = "http://$env:USERDNSDOMAIN/".toLower()

 
As they say in the SuSE Linux motd: Have a lot of fun!

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
20Sep/110

SharePoint + PowerShell: Import All BDCM Files in a Directory and Set Permissions

In order to invest as little effort as possible creating and recreating and recreating my External Content Types on my development server, I exported the ECT's then deleted External Content Types, External Systems, and BDC Models. I saved them all in a directory on the SharePoint server, then I placed ImportALLbdcms.ps1 in the same directory and ran it.

$adminGroup = "DOMAIN\SharePoint Admins" #for bdc permissions
$userGroup = "Domain\SharePoint Users"

$serviceContext = "http://localhost/"
$nameSpace = "http://$env:USERDNSDOMAIN/".toLower()

$bdc = Get-SPBusinessDataCatalogMetadataObject -BdcObjectType Catalog -ServiceContext $serviceContext
$pathtobdcmfiles = Get-Location
$importFiles = Get-Childitem -path $pathtobdcmfiles | Where {$_.extension -eq ".bdcm" -and $_.basename -ne "catalog"}

foreach ($file in $importFiles) {
Import-SPBusinessDataCatalogModel -Path $file.FullName -Identity $bdc -force -ModelsIncluded -PropertiesIncluded -PermissionsIncluded -Verbose
write-host $file.fullname
}

$claimAdmin = New-SPClaimsPrincipal -Identity $adminGroup -IdentityType WindowsSamAccountName
$claimUsers = New-SPClaimsPrincipal -Identity $userGrup -IdentityType WindowsSamAccountName

Grant-SPBusinessDataCatalogMetadataObject -Identity $bdc -Principal $claimAdmin -Right "Execute,SetPermissions,Edit,SelectableInClients"
Grant-SPBusinessDataCatalogMetadataObject -Identity $bdc -Principal $claimUsers -Right "Execute,Edit,SelectableInClients"

Copy-SPBusinessDataCatalogAclToChildren -MetadataObject $bdc

 
Sorry for a junky intro and no conclusion, I've been working 19 hour straight and I'm about exhausted. Anyway, HTHY!

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
20Sep/110

SharePoint 2010 + PowerShell: Export/Import Calendar Items

Today has been miserable. All I wanted to do was import a SharePoint 2007 calendar into SharePoint 2010 to use as sample data for a prototype site. I was caaarraaazzy enough to believe it would be a simple import/export, but no. There's no such thing. Maybe I could use Outlook calendaring or idq files as a medium? No. So I decided to do the good ol "Save this as a template and include content" workaround. Then I followed Tom's awesome instructions on how to hack the SharePoint 2007 template to get SharePoint 2010 to use it.

That worked! It imported over 3000 calendar items, but then the library itself was broken. When I'd make modifications (such as checking the box to make the calendar a resource), it would throw unknown errors IN MY FACE. So I decided I would just create a fresh SharePoint 2010 Calendar, then import the data using a PowerShell script which would just copy list item data from one calendar to another. This should work for almost any list in SharePoint. Here's how I did it, y'all:

First, create a new Calendar using the regular Calendar template. Then, run this script after changing $siteURL, $sourceListName and $destListName to suit your environment.

$siteURL = "http://sharepoint/"
$sourceListName = "Corporate Calendar"
$destListName= "Prototype Calendar"

$site = Get-SPSite $siteURL
$web = $site.RootWeb
$sourceList = $web.Lists[$sourceListName]
$destList = $web.Lists[$destListName]

$columns = $sourceList.Fields;
$sourceItems = $sourceList.GetItems();

  foreach($sourceItem in $sourceItems) {
    $newItem = $destList.AddItem();
    foreach($column in $columns) {
      if ($column.ReadOnlyField -eq $False -and $column.InternalName -ne "Attachments") {
        $newItem[$($column.InternalName)] = $sourceItem[$($column.InternalName)];
      }
    }
    $newItem.Update();
  }

$web.dispose()
$site.dispose()

 
Note that I didn't include support for attachments at this time; 8 hours after starting on this Copy Calendar Events journey began, I'm just too beat! Perhaps I'll add it later.

Update: My awesome friend Trevor shared this link with me: Create random or demo SharePoint Content with PowerShell Cool.

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
8Sep/110

SharePoint + Powershell: Remove Hold, Record Declaration on All Documents in a Library

In order to delete a Records Library or Records Center, all holds and records in a library must be removed and the Holds and Processing timer job must be run. If this criteria is not met, the "Delete this Document Library" will not be an option in the library settings.

Here is how you can do this programmatically. Note that I did not include the deletion of documents or the document library itself.

$siteURL = $args[0]
$libraryName= $args[1]

$site = Get-SPSite $siteURL
$web = $site.RootWeb
$library = $web.Lists[$libraryName]
$records = $library.Items[0]

# First, remove all holds from all items in the Library
$holdsList = $web.Lists["Holds"]
$holds = $holdsList.Items[0]
[Microsoft.Office.RecordsManagement.Holds.Hold]::RemoveHold($records,$holds,"Holds have been removed.")

# Next, undeclare all items as records. Ps. BulkUndeclareItemsAsRecords is useless.
foreach ($record in $records) {
      [Microsoft.Office.RecordsManagement.RecordsRepository.Records]::UndeclareItemAsRecord($item)
}

Start-SPTimerJob HoldProcessing

$web.Dispose()
$site.Dispose()

 
Thanks to anavijai for the sample Hold removing code.

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
7Sep/110

SharePoint 2010 + PowerShell: Delete Site Content Type

So first, you have to delete all of the content type's children or you'll receive the error "Error: content type in use." Once the dependencies are gone, you can delete the custom site content type itself.

$sitename = $args[0]
$contentType = $args[1]

$web = Get-SPWeb $sitename
$ct = $web.ContentTypes[$contentType]

if ($ct) {
$ctusage = [Microsoft.SharePoint.SPContentTypeUsage]::GetUsages($ct)
      foreach ($ctuse in $ctusage) {
        $list = $web.GetList($ctuse.Url)
        $contentTypeCollection = $list.ContentTypes;
        $contentTypeCollection.Delete($contentTypeCollection[$ContentType].Id);
        Write-host "Deleted $contentType content type from $ctuse.Url"
        }
$ct.Delete()
Write-host "Deleted $contentType from site."

} else { Write-host "Nothing to delete." }

$web.Dispose()

 
Viola.

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
6Sep/110

SharePoint 2010 + PowerShell: View Dependencies of Content Types (aka Content Type Usage)

Earlier today, I created some test Content Types while playing around with the fancy new Content Organizer. I ran into a few errors, though, and subsequently wanted to delete the dummy content types to start over. I was unable to, however, because the content types were still in use. By what? I didn't know.

Too bad SharePoint doesn't have a feature similar to SQL Server Management Studio's "View Dependencies." Till it does.. here is a PowerShell script that does the trick.

$sitename= "http://sharepoint"
$contentType = "My Custom Content Type"

$web = Get-SPWeb $sitename
$ct = $web.AvailableContentTypes[$contentType]
$ctusage = [Microsoft.SharePoint.SPContentTypeUsage]::GetUsages($ct)
     foreach ($ctuse in $ctusage) {
          $ctuse.Url
     }

$web.Dispose()

 
Looking for content type usage within subsites? A more thorough script can be found at StackExchange.

Posted by: Chrissy   Filed under: PowerShell, SharePoint No Comments
6Sep/113

PowerShell Workaround: “Replicate Directory Changes” Permissions in AD Required for SharePoint 2010 Profile Syncs

According to the SharePoint 2010 Communities FAQ, Microsoft is burdening SharePoint 2010 Administrators with new requirements to obtain Active Directory accounts with "Replicate Directory Changes" permissions because...

In order to interrogate AD about “what has changed since time xyz”, we need the replicate-directory-changes permissions on partitions being synchronized, for example the domain partition being synchronized.These permissions are needed in particular to be able to read data within the deleted objects container of the partition. Standard users do not have permissions to read the content of this container, and we cannot simply grant rights over that container to the synchronization account.

While this may be the cleanest way to go about things, it's an unfortunate requirement for SharePoint admins within organizations which understandably frown upon making such concessions for service accounts, especially when the change wasn't required for SharePoint 2007 farms.

Microsoft's recommended architecture is to to use the published information from a centralized farm but some SharePoint administrators are on their own and do not have that luxury. I encountered this issue in a lab environment and was able to circumvent it by writing a quick and dirty script to add new users from AD and sync all SharePoint users with AD. This script only addresses one site collection and does not drill down to its subsites. Run it as often as you would run a regular sync.

# SharePoint <-> AD Group Sync
# netnerds.net
#
# What it does:
# Uses LDAP to get a list of users from a specific group in AD (in this case the "All Finance Users" group)
# Enables "Allow Unsafe Updates" for the given Site Collection then returns it to its previous state.
# Adds AD Users (but ignores nested AD groups) to the specified SharePoint Group
# Syncs all Site Users with Active Directory. This includes name changes, etc.

# 1,2,3 Go
$ldappath = "LDAP://CN=All Finance Users,OU=Finance,DC=icanhas,DC=net"
$spgroupname = "Icanhas Intranet Members"
$sitename= "http://sharepoint"
$domain = $env:USERDOMAIN

$adgroup = [ADSI]($ldappath)
$spsite = SPSite($sitename)
$rootweb = $spsite.rootweb
$spgroup = $rootweb.Groups[$spgroupname]

$allowUnsafeUpdates = $spsite.AllowUnsafeUpdates
$spsite.allowUnsafeUpdates = 1

foreach ($memberDN in $adgroup.member) {

$member = [ADSI]("LDAP://$memberDN")  | where {$_.properties.objectcategory -match "CN=Person"}
     if ($member){
               $username = $domain + "\" + $member.sAMAccountName
               $spsiteuser = $rootweb.EnsureUser($username)

               $spUser = $rootweb.AllUsers[$spsiteuser]
               $spgroup.AddUser($spsiteuser)
               }
}

$spsite.allowUnsafeUpdates = $allowUnsafeUpdates

$rootweb.dispose()
$spsite.dispose()

Get-SPUser –Web $sitename| Set-SPUser –SyncFromAD

 
Note the AD Sync on the last line. Surprisingly enough, PowerShell's SyncFromAD works without explicitly allowing Replicate Directory Changes. To test this, first I tried a domain account with regular privileges then I went a step further and denied Replicate Directory Changes for that account and it still worked.

You may want to make additional changes such as: deleting users that no longer exist in AD, and replicating all changes to subsites.

Ahh, just good enough :)

Posted by: Chrissy   Filed under: Active Directory, PowerShell, SharePoint 3 Comments
25Jul/111

VBScript: When You Can’t Use Powershell to Ping a Webpage…

Sometimes, there's a need to schedule a "ping" to a webpage (that needs to be pre-compiled, for instance). While using PowerShell is the easiest way to do this, it's not always available on older server, while VBScript is nearly always a safe bet. The biggest issue with using VBScript or the command line is what to do with the window it may open. Instead of using iexplore and taskkill, using the following VBScript is my fav alternative.

Call pingPage ("http://www.domain.com")

Function pingPage (strURL)
Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP")

objXMLHTTP.open "GET", strURL, false
objXMLHTTP.send()

If objXMLHTTP.Status <> 200 Then
'email someone about it
End if

Set objXMLHTTP = Nothing
End Function


On a related note: the PowerShell line looks something like this: $pingPage = (new-object net.webclient).DownloadString("http://www.domain.com")

Posted by: Chrissy   Filed under: PowerShell, VBScript 1 Comment