magritton
12/14/2018 - 7:20 PM

Deploy Solution

The deploys a solution file , WSP, by adding and installing it in the GAC

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
Add-SPSolution "C:\deploy\xxxx.Workflow.SPDActions2016.wsp"
Install-SPSolution -Identity xxxx.Workflow.SPDActions2016.wsp -GACDeployment -CompatibilityLevel {14,15} -Force
# This allows the script to be run from the normal powershell in addition to sharepoint
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

# Set whether you want the feature.ps1 file to run in the current directory
$runFeatureFile = $false
$featureFile = ".\feature.ps1"

# Sets the max number of times we will wait for a 2 secont timer to check if the timer job is still running on a solution
$maxTimerWait = 250

$logFile = ".\WSPPowershell.log"

function main
{
    (LogWrite "")
    (LogWrite "----------------------------------------------------")
    $currentDateTime = Get-Date
    (LogWrite ("Run By:" + [Environment]::UserName))
    (LogWrite ("Run At:" + $currentDateTime))
    (LogWrite "")
    
    (LogWrite "[MAIN] Started")
    # Start all timer services on all of the servers
    $farm = Get-SPFarm
    $disabledTimers = $farm.TimerService.Instances | where {$_.Status -ne "Online"}    
    if($disabledTimers -ne $null)
    {
        foreach ($timer in $disabledTimers)
        {
            (LogWrite ("[MAIN] Timer service instance on server" + $timer.Server.Name + "is not Online. Current status:" + $timer.Status))
            (LogWrite "[MAIN] Attempting to set the status of the service instance to online")
            try
            {
                $timer.Status = [Microsoft.SharePoint.Administration.SPObjectStatus]::Online
                $timer.Update()
            }
            catch
            {
                (LogWrite ("[Main]:!!!!!!!!!" + $identity + ": Starting Timer Service FAILED!!!!!!!!!"))
                (LogWrite ($Error[0]))
                WriteHost -f Red ("[Main]:!!!!!!!!!" + $identity + ": Starting Timer Service FAILED!!!!!!!!!")
                WriteHost -f Red ($Error[0])
                Exit
            } 
        }
    }
    else
    {
        (LogWrite "[MAIN] All timer services are online.")
    }

    # Loop through all wsp files in current folder and below
    $list = Get-ChildItem -recurse | where {$_.extension -eq ".wsp" }
    foreach($wspfile in $list)
    {
		$confirmation = Read-Host "Are you sure you want to install: " $wspfile.Name " (y/n)"
		if ($confirmation -eq 'y') {
			$identity = $wspfile.Name
			$path = $wspfile.FullName
			
			(LogWrite ("[MAIN]:" + $identity + ": Solution started"))
			Write-Host -f Cyan ("[MAIN]:" + $identity + ": Solution started")

			# Check if solution already exists
			$isInstalled = Get-SPSolution | where {$_.Name -eq $identity}        
			if($isInstalled)
			{
				(LogWrite ("[MAIN]:" + $identity + ": Is already installed. Retracting the old solution and deploying the new solution"))
				(RetractSolution $identity)
				(DeploySolution $path $identity)
			}
			else
			{
				(LogWrite ("[MAIN]:" + $identity + ": Is not already installed. Deploying the new solution"))
				(DeploySolution $path $identity)
			}
			(LogWrite ("[MAIN]:" + $identity + ": Solution complete"))
			Write-Host -f Cyan ("[MAIN]:" + $identity + ": Solution complete")

			(LogWrite "")
		}
    }
    
    # Run the feature powershell file in the same directory
    if($runFeatureFile -eq $true)
    {
        # Make sure file exists
        if(Test-Path $featureFile)
        {
            $command = "$featureFile -logFile $logFile"
            Invoke-Expression $command
        }
    }
    
    $currentDateTime = Get-Date
    (LogWrite "[MAIN] Complete")
    (LogWrite ("Finished At:" + $currentDateTime))
    (LogWrite "----------------------------------------------------")
    (LogWrite "")
}


# This function attempts to add, then install the wsp file
# Parameters: 
# path - path to the wsp file including wsp file name
# identity - the name of the wsp
function DeploySolution([string]$path, [string]$identity)
{
    (LogWrite "")
    (LogWrite ("[DEPLOY]:" + $identity + ": Started"))
    Write-Host -f Green ("[DEPLOY]: Started")

    # Before starting deploy, verify there are no timer jobs related to this wsp running
    WaitForJobToFinish $identity "[DEPLOY]: Pre deploy timer job check for wsp"

    # Attempt to add the solution, catch error, log and exit if it fails
    (LogWrite ("[DEPLOY]:" + $identity + ": Adding solution"))
    try
    {
        $catchOutput = Add-SPSolution $path
    }
    catch
    {
        (LogWrite ("[DEPLOY]:!!!!!!!!!" + $identity + ": Add-SPSolution FAILED!!!!!!!!!"))
        (LogWrite ($Error[0]))
        Write-Host -f Red ("[DEPLOY]:!!!!!!!!!" + $identity + ": Add-SPSolution FAILED!!!!!!!!!")
        Write-Host -f Red ($Error[0])
        Exit
    }
    Write-Host
    
    (LogWrite ("[DEPLOY]:" + $identity + ": Done adding solution"))

    # See if solution contains a web application resource
    $solution = Get-SPSolution | where { $_.Name -match $identity}
    if($solution.ContainsWebApplicationResource)
    {
        # Attempt to install the solution, catch error, log and exit if it fails
        (LogWrite ("[DEPLOY]:" + $identity + ": Web application specific resources included. Installing Solution"))
        try
        {
            Install-SPSolution -Identity $identity -AllWebApplications -GACDeployment -force -CompatibilityLevel All -CASPolicies:$($solution.ContainsCasPolicy)
        }
        catch
        {
            (LogWrite ("[DEPLOY]:!!!!!!!!!" + $identity + ": Install-SPSolution FAILED!!!!!!!!!"))
            (LogWrite ($Error[0]))
            Write-Host -f Red ("[DEPLOY]:!!!!!!!!!" + $identity + ": Install-SPSolution FAILED!!!!!!!!!")
            Write-Host -f Red ($Error[0])
            Exit
        } 
        (LogWrite ("[DEPLOY]:" + $identity + ": Install complete"))
    }
    else
    {
        # Attempt to install the solution, catch error, log and exit if it fails
        (LogWrite ("[DEPLOY]:" + $identity + ": No web application specific resources included. Installing Solution"))
        try
        {
            Install-SPSolution -Identity $identity -GACDeployment -force -CompatibilityLevel All -CASPolicies:$($solution.ContainsCasPolicy)
        }
        catch
        {
            (LogWrite ("[DEPLOY]:!!!!!!!!!" + $identity + ": Install-SPSolution FAILED!!!!!!!!!"))
            (LogWrite ($Error[0]))
            WriteHost -f Red  ("[DEPLOY]:!!!!!!!!!" + $identity + ": Install-SPSolution FAILED!!!!!!!!!")
            WriteHost -f Red  ($Error[0])
            Exit
        } 
        (LogWrite ("[DEPLOY]:" + $identity + ": Install complete"))
    }

    # Wait for timer to complete the previous task before saying deploy succeded
    WaitForJobToFinish $identity "[Deploy] Wait for timer job to deploy wsp"
    (LogWrite ("[DEPLOY]:" + $identity + ": Complete"))
    Write-Host -f Green ("[DEPLOY]: Complete")
    (LogWrite "")
}


# This function attempts to uninstall, then remove the wsp file
# Parameters: 
# identity - the name of the wsp
function RetractSolution([string]$identity)
{
    (LogWrite "")
    (LogWrite ("[RETRACT]:" + $identity + ": Started"))
    Write-Host -f White ("[RETRACT]: Started")

    # Before starting retract, verify there are no timer jobs related to this wsp running
    WaitForJobToFinish $identity "[RETRACT]: Pre retract timer job check for wsp"

    # Only unistall if solution is deployed. Also make the call to all web applications if appropriate
    $solution = Get-SPSolution | where { $_.Name -match $identity}
    if($solution.ContainsWebApplicationResource -and $solution.Deployed -eq $true)
    {
        (LogWrite ("[RETRACT]:" + $identity + ": Web application specific resources included. Uninstalling Solution"))
        try
        {
            Uninstall-SPSolution -Identity $identity -AllWebApplications -Confirm:$false
        }
        catch
        {
            (LogWrite ("[RETRACT]:!!!!!!!!!" + $identity + ": Uninstall-SPSolution FAILED!!!!!!!!!"))
            (LogWrite ($Error[0]))
            WriteHost -f Red ("[RETRACT]:!!!!!!!!!" + $identity + ": Uninstall-SPSolution FAILED!!!!!!!!!")
            WriteHost -f Red ($Error[0])
            Exit
        } 
        (LogWrite ("[RETRACT]:" + $identity + ": Uninstall complete"))

        # Wait for timer to complete this task
        WaitForJobToFinish $identity "[RETRACT] Wait for timer job to uninstall wsp"
    }
    elseif($solution.Deployed -eq $true)
    {
        (LogWrite ("[RETRACT]:" + $identity + ": No web application specific resources included. Uninstalling Solution"))
        try
        {
            Uninstall-SPSolution -Identity $identity -Confirm:$false
        }
        catch
        {
            (LogWrite ("[RETRACT]:!!!!!!!!!" + $identity + ": Uninstall-SPSolution FAILED!!!!!!!!!"))
            (LogWrite ($Error[0]))
            WriteHost -f Red ("[RETRACT]:!!!!!!!!!" + $identity + ": Uninstall-SPSolution FAILED!!!!!!!!!")
            WriteHost -f Red ($Error[0])
            Exit
        } 
        (LogWrite ("[RETRACT]:" + $identity + ": Uninstall complete"))

        # Wait for timer to complete this task
        WaitForJobToFinish $identity "[RETRACT] Wait for timer job to uninstall wsp"
    }

    (LogWrite ("[RETRACT]:" + $identity + ": Removing"))
    try
    {
        Remove-SPSolution -Identity $identity -Confirm:$false
    }
    catch
    {
        (LogWrite ("[RETRACT]:!!!!!!!!!" + $identity + ": Remove-SPSolution FAILED!!!!!!!!!"))
        (LogWrite ($Error[0]))
        WriteHost -f Red ("[RETRACT]:!!!!!!!!!" + $identity + ": Remove-SPSolution FAILED!!!!!!!!!")
        WriteHost -f Red ($Error[0])
        Exit
    }
    # Wait for timer to complete the previous task before saying retract succeded
    WaitForJobToFinish $identity "[Deploy] Wait for timer job to deploy wsp"

    (LogWrite ("[RETRACT]:" + $identity + ": Removing complete"))
    (LogWrite ("[RETRACT]:" + $identity + ": Complete"))
    Write-Host -f White ("[RETRACT]: Complete")
    (LogWrite "")
}

# This function waits until the wsp file ($identity) has no timer jobs being run on it. If it hits max wait time it will exit and error out as 
# other SharePoint commands cannot be run against it.
# Parameters:
# identity - the name of the wsp
# callingPurpose - used to log where the script shutdown if timer job is unsuccessful
function WaitForJobToFinish([string]$identity, [string]$callingPurpose)
{
    (LogWrite "")
    $job = Get-SPTimerJob | ?{$_.Name -like "*solution-deployment*$identity*"}
    $currentwait = 0

    if(!$job)
    {
        (LogWrite ("[WAITTIMER]" + $identity + ": No timer jobs for this wsp found. Continuing on."))
    }
    else
    {
        $jobName = $job.Name
        (LogWrite ("[WAITTIMER]:" + $identity + ": Waiting to finish job $jobName"))
        Write-Host -f Yellow ("[WAITTIMER]:" + $identity + ": Waiting on timer job to finish...")
        while(($currentwait -lt $maxTimerWait))
        {
            $currentwait = $currentwait + 1
            Start-Sleep -Seconds 2
            if(!(Get-SPTimerJob $jobName))
            {
                break;
            }
        }

        # If we made it through the max wait time, the timer job is taking longer than expected and never finished
        if($currentwait -eq $maxTimerWait)
        {
            $timerJobTimeoutError = "failed. Timer job hasn't finished within time allowed. Exiting in current state."
            (LogWrite ("[WAITTIMER]:TimeWaited" + ($currentwait * 2) + ":" + $identity + ":" + $callingPurpose + $timerJobTimeoutError))
            Write-Host -f Red ("[WAITTIMER]:TimeWaited" + ($currentwait * 2) + ":" + $identity + ":" + $callingPurpose + $timerJobTimeoutError)
            Exit
        }
        else
        {
            (LogWrite ("[WAITTIMER]:TimeWaited" + ($currentwait * 2) + ":" + $identity + ": Timer job complete"))
            Write-Host -f Yellow ("[WAITTIMER]" + $identity + ": Timer job complete")
        }
    }
    (LogWrite "")
}


# Function used to log to the log file
function LogWrite([string]$logString)
{
    Add-Content $logFile -value $logString
}

main