Creating Fields and Content Types cleanly in SharePoint

when creating Content Types in SharePoint, it’s nice to script the process, for planning and repeatability. Here’s a nifty routine that allows a single line creation of Site Columns and Content Types.

First, here’s the routine to create Site Columns. The here-string XML is replaced with placeholders filled by parameters. Note the convenient parameters:

function tryAdd-TextField ([Microsoft.SharePoint.SPWeb] $web, [string] $DisplayName, [string] $InternalName, [string] $Group, [Boolean] $Hidden, 
[Boolean] $Required, [Boolean] $ShowInDisplayForm, [Boolean] $ShowInEditForm, [Boolean] $ShowInNewForm )
{
$fields = $web.fields;
try
{
	$q=$fields.getFieldByInternalName($InternalName); 
} catch
{
	$q=$null;
}

if ($q -ne $null)
{
Write-Host "$($InternalName) already exists!, no action taken to create this site column"
}
else
{
$fieldXMLString = '<Field Type="Text"
Name="@InternalName" 
DisplayName="@DisplayName"
StaticName="@InternalName"
Group="@Group"
Hidden="@Hidden"
Required="@Required"
Sealed="FALSE"
ShowInDisplayForm="@ShowInDisplayForm"
ShowInEditForm="@ShowInEditForm"
ShowInListSettings="TRUE"
ShowInNewForm="@ShowInNewForm">
 </Field>' 

 $FieldXMLString = $FieldXMLString.Replace("@InternalName",$InternalName.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@DisplayName",$DisplayName.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@Group",$Group.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@Hidden",$Hidden.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@Required",$Required.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@ShowInDisplayForm",$ShowInDisplayForm.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@ShowInEditForm",$ShowInEditForm.tostring())
 $FieldXMLString = $FieldXMLString.Replace("@ShowInNewForm",$ShowInNewForm.tostring())

 $web.Fields.AddFieldAsXml($fieldXMLString)
 }
 }
function tryAdd-CT ([Microsoft.SharePoint.SPWeb] $web, [string] $Field, [string] $Group, [string] $Parent, [string] $Name, [string] $Description )
{
    try
    {
        $ctypeParent = $web.availablecontenttypes[$Parent]

    }
    catch
    {
        $ctypeParent = $null;
    }

    if ($ctypeParent -eq $null)
    {
        write-host "Content Type $($Name) not created because there was a problem finding the Parent Content Type $($Parent)"
    }
    else
    {
    $ctype = new-object Microsoft.SharePoint.SPContentType($ctypeParent, $web.contenttypes, $Name)
	$ctype.Description = $Description
	$ctype.group = $Group

    if (![string]::IsNullOrEmpty($field))
    {
    foreach ($fld in $field.split("|"))
     {
        $f=$web.fields.getFieldByInternalName($fld)  
        $link = new-object Microsoft.SharePoint.SPFieldLink $f
        $ctype.FieldLinks.Add($link)
     }
    }

    try
    {
	    $ctype = $web.contenttypes.add($ctype)
    }
    catch
    {
        write-host "Content Type $($Name) already exists"
    }
    }
}

Let’s now create some site columns:

 tryAdd-TextField -web $Web -DisplayName "Year"    -InternalName "YearACT"    -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
 tryAdd-TextField -web $Web -DisplayName "Quarter" -InternalName "QuarterACT" -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
 tryAdd-TextField -web $Web -DisplayName "Month"   -InternalName "MonthACT"   -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
 tryAdd-TextField -web $Web -DisplayName "LOB"     -InternalName "LOBACT"     -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 

Here’s how to create a Content Type with one field:

tryAdd-CT -web $Web -Field $null -Group "Actuarial"  -Parent (select-parent)  -Name "My Core ACT" -Description "Core Actuarial Document"

Note in the comment below, the fields can be passed in pipe-delimited

tryAdd-CT -web $Web -Field "YearACT|QuarterACT|MonthACT|LOBACT" -Group "Actuarial"  -Parent "My Core ACT" -Name "General (ACT)" -Description "General Actuarial Document"

Automate Migration of FTP files to a File Share

A common business process to automate is moving files from an FTP server. To copy the files from an FTP server we first need to get a file listing. The following routine serves nicely:

function Get-FtpDir ($url,$credentials) {
    $request = [Net.WebRequest]::Create($url)
    $request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
    if ($credentials) 
    { 
        $request.Credentials = $credentials 
    }
    $response = $request.GetResponse()
    $reader = New-Object IO.StreamReader $response.GetResponseStream() 
	$reader.ReadToEnd()
	$reader.Close()
	$response.Close()
}

Let’s set some basic parameters for the file migration:

 $url = "ftp://sftp.SomeDomain.com/"
 $user = "UserID" 
 $pass = 'Password'  #single quotes recommended if unusual characters are in use
 $DeleteSource = $true;  #controls whether to delete the source files from the FTP Server after copying
 $DestLocation = "\\DestinationServer\AnyLocation\";

Let’s start the processing with connecting to the FTP server, getting the file list, and processing:

 $credentials = new-object System.Net.NetworkCredential($user, $pass) 
 $webclient = New-Object System.Net.WebClient 
 $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)  

$files=Get-FTPDir $url $credentials
$filesArr = $files.Split("`n")

Foreach ($file in ($filesArr )){
	if ($file.length -gt 0) #this actually happens for last file
	{
	$source=$url+$file 
	$dest = $DestLocation + $file
	$dest = $dest.Trim()		# this is actually needed, as trailing blank appears in FTP directory listing
	$WebClient.DownloadFile($source, $dest)
	}
}

Let’s now delete the source files, if configured to do so:

if ($DeleteSource)
{
Foreach ($file in ($filesArr )){
	if ($file.length -gt 0) #this actually happens for last file
	{
	$source=$url+$file 
	$ftprequest = [System.Net.FtpWebRequest]::create($source)
	$ftprequest.Credentials =  New-Object System.Net.NetworkCredential($user,$pass)
	$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile
	$ftprequest.GetResponse()
	}
}
}

That’s it in a nutshell. Just don’t try to move zero length files, and trim the filenames for trailing blanks. Ensure your FTP ports are open, and you are good to go!

Folder, File size and timestamp analysis using PowerShell

In just a few lines, one can generate a CSV of all files and their attributes including size and timestamp. I prefer to use TSV (tab delimited) to ensure embedded characters like quotes or commas don’t disrupt the CSV layout.

$q = Get-ChildItem "\\server\Reserve" -Recurse
#$q | ConvertTo-Csv -NoTypeInformation | out-file "D:\Div\Act\Analytics\ACTRDriveFoldersFiles.csv" 
$q2 = $q | select PSPath,PSChildName,	PSIsContainer,BaseName,Length,Name,FullName,Extension,CreationTime,LastAccessTime,LastWriteTime	

foreach ($qItem in $q2)
{
   $qItem.PSPath = $qItem.PSPath.replace("Microsoft.PowerShell.Core\FileSystem::","")
}

$q2 | ConvertTo-Csv -NoTypeInformation -delimiter "`t" | out-file "D:\myFolder\Report.csv"