I'm deploying apps using Import-SPAppPackage and Install-SPApp. I'd like to be able to use Set-AppPrincipalPermission to set permissions but I can't get it working.
I'm uploading a SharePoint-hosted app to SharePoint using the PowerShell cmdlets Import-SPAppPackage and Install-SPApp. This is working fine for SharePoint-hosted apps that do not require additional permissions.
However, one app needs read access to the site, so this is declared in the manifest. And it works fine when run through Visual Studio - on first launch, it correctly asks to trust the app for read access to the site.
When I add this app via PowerShell, it has no opportunity to ask. The install continues without problems, but then the app doesn't work. (It fails with a permissions problem, which is absolutely the correct behavour since the permissions haven't yet been granted.)
I can fix the permissions by going to the Site Contents, clicking on the '...' for the problem app, choosing 'Permissions' and then clicking the link that says 'If there's something wrong with the app's permissions, click here to trust it again'.
But I really want to just be able to do the whole deployment via PowerShell.
The Set-AppPrincipalPermission cmdlet should allow me to set the permissions, but I can't get it to work. Specifically, I can't get a handle on the app principal that was automatically created when the app was deployed, so I can't pass this app principal to Set-AppPrincipalPermission.
The app principal has a name of the form 'i:0i.t|ms.sp.int|#' and it is listed on /_layouts/15/appprincipals.aspx. When I use Get-SPAppPrincipal with it, all I get is:
Get-SPAppPrincipal : The app principal could not be found.
I haven't seen any examples of using Get-SPAppPrincipal for any SharePoint-hosted apps - they all seem to be for provider-hosted apps. They also all seem to just use an app principal ID built from the client ID and the realm ID, but my SharePoint-hosted app doesn't have a client ID.
Is it possible to get the app principal of a SharePoint-hosted app and use it to set the permissions via PowerShell? Am I doing something wrong, or is there another approach?
I struggled the same problem like you and finally found an answer in these two blogs:
Blog with a nice Install, Update and Delete Script
Here is a nice post about "pressing" the "Trust It" Button via PowerShell Link
And because I know how lazy programmers like me are, feel free to use this merged script to Install Apps:
param
(
[string]$Web = $(throw '- Need a SharePoint web site URL (e.g. "http://portal.contoso.com/")'),
[string]$Source = "ObjectModel"
)
Write-Host -ForegroundColor White "-------------------"
Write-Host -ForegroundColor White "| App Installer |"
Write-Host -ForegroundColor White "-------------------"
Write-Host -ForegroundColor White "- "
#Global vars
$AppPackageName = "App.app";
#Loads powershell settings
Write-Host -ForegroundColor White "- Load Powershell context.."
$0 = $myInvocation.MyCommand.Definition
$dp0 = [System.IO.Path]::GetDirectoryName($0)
#Loads the SharePoint snapin
Write-Host -ForegroundColor White "- Load SharePoint context.."
$ver = $host | select version
if ($ver.Version.Major -gt 1) {$host.Runspace.ThreadOptions = "ReuseThread"}
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
Add-PSSnapin "Microsoft.SharePoint.PowerShell";
}
[void][System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
#Imports the App package
Write-Host -ForegroundColor White "- Import app package '$AppPackageName'..."
$appPath = "C:\Projects\App\App\bin\Debug\app.publish\1.0.0.0" + "\" + $AppPackageName;
if ($Source.Equals("ObjectModel", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::ObjectModel);
}
elseif ($Source.Equals("Marketplace", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::Marketplace);
}
elseif ($Source.Equals("CorporateCatalog", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::CorporateCatalog);
}
elseif ($Source.Equals("DeveloperSite", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::DeveloperSite);
}
elseif ($Source.Equals("RemoteObjectModel", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::RemoteObjectModel);
}
$spapp = Import-SPAppPackage -Path "$appPath" -Site $Web -Source $sourceApp -Confirm:$false -ErrorAction SilentlyContinue -ErrorVariable err;
if ($err -or ($spapp -eq $null))
{
Write-Host -ForegroundColor Yellow "- An error occured during app import !"
throw $err;
}
Write-Host -ForegroundColor White "- Package imported with success."
#Installs the App
Write-Host -ForegroundColor White "- Install the APP in web site..."
$app = Install-SPApp -Web $Web -Identity $spapp -Confirm:$false -ErrorAction SilentlyContinue -ErrorVariable err;
if ($err -or ($app -eq $null)) {
Write-Host -ForegroundColor Yellow "- An error occured during app installation !"
throw $err;
}
$AppName = $app.Title;
Write-Host -ForegroundColor White "- App '$AppName' registered, please wait during installation..."
$appInstance = Get-SPAppInstance -Web $Web | where-object {$_.Title -eq $AppName};
$counter = 1;
$maximum = 150;
$sleeptime = 2;
Write-Host -ForegroundColor White "- Please wait..." -NoNewline;
$url = "$($Web)_layouts/15/appinv.aspx?AppInstanceId={$($appInstance.Id)}"
$ie = New-Object -com internetexplorer.application
try
{
$ie.visible=$true
$ie.navigate2($url)
while ($ie.busy)
{
sleep -milliseconds 60
}
$trustButton = $ie.Document.getElementById("ctl00_PlaceHolderMain_BtnAllow")
$trustButton.click()
sleep -Seconds 1
Write-Host "App was trusted successfully!"
}
catch
{
throw ("Error Trusting App");
}
while (($appInstance.Status -eq ([Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installing)) -and ($counter -lt $maximum))
{
Write-Host -ForegroundColor White "." -NoNewline;
sleep $sleeptime;
$counter++;
$appInstance = Get-SPAppInstance -Web $Web | where-object {$_.Title -eq $AppName}
}
Write-Host -ForegroundColor White ".";
if ($appInstance.Status -eq [Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installed) {
Write-Host -ForegroundColor White "- The App was successfully installed.";
$appUrl = $appInstance.AppWebFullUrl;
Write-Host -ForegroundColor White "- The App is now available at '$appUrl'.";
Write-Host -ForegroundColor White "- (Don't forget to add app host name in your host file if necessary...).";
Write-Host -ForegroundColor White "- "
}
else {
Write-Host -ForegroundColor Yellow "- An unknown error has occured during app installation. Read SharePoint log for more information.";
}
Figured out a way other than using IE.
Basically just using powershell to call SPAppPrincipalPermissionsManager.AddAppPrincipalToWeb
$rootUrl = "https://ur-sp.com"
$urlSiteName = "ur-site"
$web = Get-SPWeb "$rootUrl/$urlSiteName"
$appPrincipalManager = [Microsoft.SharePoint.SPAppPrincipalManager]::GetManager($web)
$applicationEndPointAuthorities = new-object System.Collections.Generic.List[string]
$applicationEndPointAuthorities.Add("$rootUrl/$urlSiteName");
$symmetricKey = New-Object System.Security.SecureString;
$datetimeNow = [System.DateTime]::Now
$credential = [Microsoft.SharePoint.SPAppPrincipalCredential]::CreateFromSymmetricKey($symmetricKey,$datetimeNow,$datetimeNow)
$creationParameters =New-Object Microsoft.SharePoint.SPExternalAppPrincipalCreationParameters($appid,$appFriendlyName,$applicationEndPointAuthorities,$credential)
$appPrincipal = $appPrincipalManager.CreateAppPrincipal($creationParameters)
$appPrincipalPermissionsManager = New-Object -TypeName
Microsoft.SharePoint.SPAppPrincipalPermissionsManager -ArgumentList $web
$r = $appPrincipalPermissionsManager.AddAppPrincipalToWeb($appPrincipal, 3)
3 is of SPAppPrincipalPermissionKind enum, and I don't think its value really matters.
This will do the full trust part via powershell:
$targetWeb = Get-SPSite "http://dev.my.com"
$clientID = "82ea34fc-31ba-4e93-b89a-aa41b023fa7e"
$authRealm = Get-SPAuthenticationRealm -ServiceContext $targetWeb
$AppIdentifier = $clientID + "#" + $authRealm
$appPrincipal = Get-SPAppPrincipal -Site $targetWeb.RootWeb -NameIdentifier $AppIdentifier
Set-SPAppPrincipalPermission -Site $targetWeb.RootWeb -AppPrincipal $appPrincipal -Scope SiteCollection -Right FullControl
More info here:
http://lixuan0125.wordpress.com/2013/11/18/register-and-install-app-through-powershell/
Related
Hey Everyone so I am able to set the sharepoint sites sensitivity labels in my tenant by doing them individually using the following commands:
# Get the label (required ExchangeOnlineManagement module)
Connect-IPPSSession -userprincipalname wise#redacted
$c = Get-Credential wise#redacted
Connect-SPOService -Url https://redacted-admin.sharepoint.com -Credential $c -ModernAuth:$true -AuthenticationUrl https://login.microsoftonline.com/organizations
Set-SPOSite -identity {site_url} -SensitivityLabel:'{GUID of sens label here}'
And now I am trying to set a default label for all sites that have not been manually set by users with the following code, but its throwing the "Site already has a sensitivity label" message, meaning the if statement isnt triggering when ran from a site in a variable???
# Get the label (required ExchangeOnlineManagement module)
Connect-IPPSSession -userprincipalname wise#redacted
$c = Get-Credential wise#redacted
Connect-SPOService -Url https://redacted-admin.sharepoint.com -Credential $c -ModernAuth:$true -AuthenticationUrl https://login.microsoftonline.com/organizations
#Create a progress counter and fail counter
$count = 0
$fail = 0
#get all sites
write-host "Collecting site data" -ForegroundColor Yellow
$SiteCollections = Get-SPOSite -Limit All
write-host "Collecting site data - COMPLETED" -ForegroundColor Green
#get a count of total sites
$total = $SiteCollections.count
write-host "There are $total total sites." -ForegroundColor Green
foreach ($site in $SiteCollections){
$count++
try{
if ( $site.SensitivityLabel -eq '' ){
Set-SPOSite $site -SensitivityLabel:'{GUID}'
} else {
$label = $site.SensitivityLabel
Write-host "Site already has a sensitivity label: $label"
$site.Url | Out-File '.\Sites_already_labeled.txt' -Append
}
} catch {
$sitename = $site.Name
"Bad site: $sitename" | Out-File '.\Failed_change_sites.txt' -Append
$fail++
Write-Host "Failed site count = $fail" -ForegroundColor Blue
Write-Host "Failed site = $sitename" -ForegroundColor Blue
}
Write-Host "Processing Site $count of $total" -ForegroundColor Red
}
I have tested the if sens label = '' on singular sites and it does infact fufill the if statement so im completely lost.
Thanks ChatGPT!
It's likely that the issue is with the following line of code: if ( $site.SensitivityLabel -eq '' ). The SensitivityLabel property of a SharePoint Online site may not be an empty string when it has no label applied to it. Instead, it may be $null, so the if statement should be updated to if ( $site.SensitivityLabel -eq $null ).
I'm working on a script that gets executed only if X account is found, but is not working as intended the if/else statements get bypassed and the code gets executed anyways. What am i doing wrong?
$Account = "XXXX"
Get-LocalUser -name $Account
if (($Account) -eq $true) {
} else {
Write-host -foreground cyan "I found it"
}
exit
If i ran the script as is it will output the text on the console even tho "XXX" account is not present, could something like that can be done?
This should do it:
$Account = "XXXX"
$AccountObject=Get-LocalUser -name $Account -ErrorAction SilentlyContinue
if (($AccountObject)) {
Write-host -foreground cyan "I found it"
} else {
Write-host -foreground cyan "No luck"
}
The issue with the sniplet provided - the return of Get-LocalUser was not used. Instead you were using a string value which is always set therefore true - as you set it to 'XXXX' in your first line.
As Bill_Stewart explains, the reason that the else block is reached is because ($Account) -eq $true evaluates to $false unless the account name is "true".
In order to test whether Get-LocalUser succeeded or failed to retrieve the user account, you can instead inspect the $? automatic variable - it will have a value of $false only if the previous command threw an error:
$AccountName = "nonExistingUser"
# Try to fetch existing user account, don't show any errors to the user
$UserAccount = Get-LocalUser -Name $AccountName -ErrorAction SilentlyContinue
# Test if the call was successful
if($?) {
Write-Host "Found account named '$AccountName'!" -ForegroundColor Cyan
$UserAccount
} else {
Write-Host "No account named '$AccountName' was found ..." -ForegroundColor Magenta
}
I work in a large company and that is global and I would like to be able to run a script that will allow me to clean up old profile remotely. I found this script but it doesn't seem to do anything. Does anyone have thought to why. I am a novice in powershell so any help would be appreciated. Thanks in advance!
function Remove-ProfileWD {
<#
.SYNOPSIS
Interactive menu that allows a user to connect to a local or remote computer and remove a local profile.
.DESCRIPTION
Presents an interactive menu for user to first make a connection to a remote or local machine. After making connection to the machine,
the user is presented with all of the local profiles and then is asked to make a selection of which profile to delete. This is only valid
on Windows Vista OS and above for clients and Windows 2008 and above for server OS.
.EXAMPLE
Remove-ProfileWF
Description
-----------
Presents a text based menu for the user to interactively remove a local profile on local or remote machine.
#>
#Prompt for a computer to connect to
(
[Parameter(Mandatory=$true)]
$Computer = Read-Host "Please enter a computer name"
)
#Test network connection before making connection
If ($computer -ne $Env:Computername) {
If (!(Test-Connection -comp $computer -count 1 -quiet)) {
Write-Warning "$computer is not accessible, please try a different computer or verify it is powered on."
Break
}
}
Do {
#Gather all of the user profiles on computer
Try {
[array]$users = Get-WmiObject -ComputerName $computer Win32_UserProfile -filter "LocalPath Like 'C:\\Users\\%'" -ea stop
}
Catch {
Write-Warning "$($error[0]) "
Break
}
#Cache the number of users
$num_users = $users.count
Write-Host -ForegroundColor Green "User profiles on $($computer):"
Write-Host -ForegroundColor Green "User profile Last Use ate"
#Begin iterating through all of the accounts to display
For ($i=0;$i -lt $num_users; $i++) {
Write-Host -ForegroundColor Green "$($i): $(($users[$i].localpath).replace('C:\Users\','')) $(get-item \\$computer\C`$\users\$(($users[$i].localpath).replace('C:\Users\',''))| Foreach {$_.LastWriteTime}) "
}
Write-Host -ForegroundColor Green "q: Quit"
#Prompt for user to select a profile to remove from computer
Do {
$account = Read-Host "Select a number to delete local profile or 'q' to quit"
#Find out if user selected to quit, otherwise answer is an integer
If ($account -NotLike "q*") {
$account = $account -as [int]
}
}
#Ensure that the selection is a number and within the valid range
Until (($account -lt $num_users -AND $account -match "\d") -OR $account -Like "q*")
If ($account -Like "q*") {
Break
}
Write-Host -ForegroundColor Yellow "Deleting profile: $(($users[$account].localpath).replace('C:\Users\',''))"
#Remove the local profile
($users[$account]).Delete()
Write-Host -ForegroundColor Green "Profile: $(($users[$account].localpath).replace('C:\Users\','')) has been deleted"
#Configure yes choice
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","Remove another profile."
#Configure no choice
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No","Quit profile removal"
#Determine Values for Choice
$choice = [System.Management.Automation.Host.ChoiceDescription[]] #($yes,$no)
#Determine Default Selection
[int]$default = 0
#Present choice option to user
$userchoice = $host.ui.PromptforChoice("","Remove Another Profile?",$choice,$default)
}
#If user selects No, then quit the script
Until ($userchoice -eq 1)
}
We are in the process of migrating user mailboxes from on-prem exchange to office 365. Since we are only doing few users at a time, our requirements were to sync local outlook client signatures into office 365 OWA portal. So user sees the same signature on their OWA portal.
Has anyone done this?
I have figured it out. So I have created a script to help our migration team to upload local outlook client signature to office 365.
You need to have admin access to user PC and exchange online admin privileges such as Global Administrator.
Here is the code:
write-host "`n`n`n`n"
#initializing variables
$username = $null
$computer = $null
$path = $null
$number = $null
$i = 0
$select = $null
$signs = $null
$chkerr = $false
$session = $null
#Run a continous loop
do {
#if powershell is not connected to office 365 then run the connection code below
if ($session.State -ne "opened") {
Write-Host "`nChecking connection to Office 365: " -NoNewline
Write-Host "Inactive!" -ForegroundColor white -BackgroundColor red
Write-Host "`nConnecting to Office 365 account...`n"
#Get user office 365 credentials
$UserCredential = Get-Credential
#create session
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
#start the session to office 365
Import-PSSession $Session
Sleep 4
}
#if the session to 365 is success then run the code below
if ($?) {
Write-Host "`nChecking connection to Office 365: " -NoNewline
Write-Host "Active!" -ForegroundColor Black -BackgroundColor green
#Start a loop to prompt for username and machinename
do {
#this variable is used below and set as false if there is an error. Also, shows a message that user need to re-enter the information
$chkerr = $false
if ($chkerr) {write-host "`nPlease try again..." -BackgroundColor yellow -ForegroundColor black}
#store username and machine name
$username = Read-Host "`n`nPlease enter user name"
$computer = Read-Host "Please enter computer name"
#if machine is not pingable then show an error
if (!(Test-Connection $computer -Count 1) ) {Write-Host "`nError: Computer is offline or name is incorrect" -ForegroundColor white -BackgroundColor red; $chkerr = $true}
else {
#if machine is pingable then check if signature direcotry exists
if (!(Test-Path "\\$computer\c$\Users\$username\AppData\Roaming\Microsoft\Signatures\")) { Write-Host "`nError: Outlook signature directory does not exists `nOR `nUsername is incorrect!" -ForegroundColor white -BackgroundColor red;$chkerr = $true}
}
#show error msg if username or machine name is left blank
if ($username -eq $null -or !$username -or $computer -eq $null -OR !$computer) {Write-Host "`nError: Username or computer name cannot be left empty" -ForegroundColor white -BackgroundColor red;$chkerr = $true }
#repeatedly ask for username and machine name until user gets it right or moves on to next username and machine name
} while ($chkerr)
#set the path to signature directory
$path = "\\$computer\c$\Users\$username\AppData\Roaming\Microsoft\Signatures"
#get a list of signature files and store them in a variable
$number = Get-ChildItem "$path\*.htm"
#check if there are any signature files at all
if ($number -or $number -ne $null) {
#start a loop
do {
#if there are multiple signature files found then show them on screen
Write-Host "`nSelect one for following file:`n"
for ([int]$i=0;$i -lt $number.count; $i++) {
Write-Host ("$i" +' --> ' + $number[$i].name)
}
#if there is more than one signature file then prompt user to select one
if ($number.count -gt 1) {
$select = Read-Host "`nPlease enter your selection"
#show error if user enters a number that is of selection
if ([int]$select+1 -gt [int]$number.Count) {Write-Host "`nError: Please enter a valid selections!" -ForegroundColor white -BackgroundColor red}
} else {
#if only one signature file is found then do not prompt and select that one file as default
$select = "0"
Write-Host "`nOnly one file found, selecting default file -->" $number[$select].name -ForegroundColor black -BackgroundColor yellow
}
#repeat the loop until user selects atleast one signature file
} while ([int]$select+1 -gt [int]$number.Count)
#show the selected signature file
Write-Host "You have selected following signature file:" $number[$select].fullname
sleep 3
#grab the html file and store it into a variable
Write-Host "`nImporting signatures into powershell"
$signs = Get-Content $number[$select].fullname
#show error if the signature import fails
if (!$?) {Write-Host "`nError: Unable to import signature into powershell!" -ForegroundColor white -BackgroundColor red}
else {
Write-Host "`nSuccess!" -ForegroundColor Black -BackgroundColor green
Write-Host "`nImporting signatures to Office 365 account of $username"
#import the signature into users OWA and set it to be used during replies, forwards and message creations.
Get-Mailbox $username | Set-MailboxMessageConfiguration -SignatureHtml $signs -AutoAddSignature:$true -AutoAddSignatureOnReply:$true -AutoAddSignatureOnMobile:$true
if (!$?) {Write-Host "`nError: Unable to import signature into Office 365 account!" -ForegroundColor white -BackgroundColor red}
else {Write-Host "`nSuccess!" -ForegroundColor Black -BackgroundColor green}
}
}
else {
#show error if no signature files are found
Write-Host "`nError: No signature files found!" -ForegroundColor white -BackgroundColor red
}
}
else {
# show error if we are unable to connect to office 365 account.
Write-Host "`nError: Connection to office 365 is required for this script to work" -ForegroundColor white -BackgroundColor red
}
} while ($true)
Successful test of Output:
Making connection:
After successfull connection:
After entering username and password:
After making the selection:
OWA view:
Error checking against user input:
Entering wrong office 365 credentials:
Entering correct computer name but wrong username:
Entering correct username but wrong computer name:
Entering wrong selecting that is out of the provided set of options:
You can also modify the script to grab input from an excel sheet.
I been trying to add an import-csv into a script from technet to create sub sites on SharePoint Online. End result would be having sub sites made from a CSV. E.G. Year 7, Year 8. Year 9.
I have done the following:
Import-Csv 'C:\sp\import.csv'|`
ForEach-Object{
$Urlsub = $_.Urlsub
$Title = $_.Title
}
$wci.Url = $Urlsub
$wci.Title = $Title
The CSV:
Urlsub Title
------ -----
Year7 Year 7
Year8 Year 8
This works fine for only the last row in the CSV. It seems to overwrite everything leaving me with the last row.
If I change it to:
$Urlsub += $_.Urlsub
It will add all the columns for $Urlsub to the same array.
How can I go about importing the CSV without it overwriting the previous value ?
Full script without csv:
#Credentials to connect to office 365 site collection url
$url ="xxx"
$username="xxx"
$password="xxx"
$Password = $password |ConvertTo-SecureString -AsPlainText -force
Write-Host "Load CSOM libraries" -foregroundcolor black -backgroundcolor yellow
Set-Location $PSScriptRoot
Add-Type -Path (Resolve-Path "Microsoft.SharePoint.Client.dll")
Add-Type -Path (Resolve-Path "Microsoft.SharePoint.Client.Runtime.dll")
Write-Host " CSOM libraries" -foregroundcolor black -backgroundcolor yellow
Set-Location $PSScriptRoot
Add-Type -Path (Resolve-Path "Microsoft.SharePoint.Client.dll")
Add-Type -Path (Resolve-Path "Microsoft.SharePoint.Client.Runtime.dll")
Write-Host "CSOM libraries loaded successfully" -foregroundcolor black -backgroundcolor Green
Write-Host "authenticate to SharePoint Online Tenant site $url and get ClientContext object" -foregroundcolor black -backgroundcolor yellow
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)
$Context.Credentials = $credentials
$context.RequestTimeOut = 5000 * 60 * 10;
$web = $context.Web
$site = $context.Site
$context.Load($web)
$context.Load($site)
try
{
$context.ExecuteQuery()
Write-Host "authenticateed to SharePoint Online site collection $url and get ClientContext object succeefully" -foregroundcolor black -backgroundcolor Green
}
catch
{
Write-Host "Not able to authenticateed to SharePoint Online site collection $url $_.Exception.Message" -foregroundcolor black -backgroundcolor Red
return
}
#creating site using WebCreationInformation calss
Write-Host "creating subsite using custom webtemplate" -foregroundcolor black -backgroundcolor yellow
$wci = New-Object Microsoft.SharePoint.Client.WebCreationInformation
$wci.Url = "Year7"
$wci.Title = "Year 7"
$wci.UseSamePermissionsAsParentSite = $true
$wci.WebTemplate = "{D0714A63-356A-4B73-815B-6E1DF824237F}#Template"
$wci.Language = 1033
$blogWeb = $site.RootWeb.Webs.Add($wci);
try
{
$context.ExecuteQuery();
Write-Host "Sub site created successfully using custom webtemplate" -foregroundcolor black -backgroundcolor Green
}
catch
{
Write-Host "Error while creating the Sub site using custom webtemplate" $_.Exception.Message -foregroundcolor black -backgroundcolor RED
}
You end up with the last value because your variable assignment falls outside of the loop which is reading the CSV file. As such, you get the last thing the variable was set to (the last line from the CSV).
I expect you'll need to create instances of WebCreationInformation for each row in the csv file, then add each of those to $site.RootWeb.Webs as I attempt to do in this example.
Import-Csv 'C:\sp\import.csv' | ForEach-Object{
$wci = New-Object Microsoft.SharePoint.Client.WebCreationInformation
$wci.Url = $_.Urlsub
$wci.Title = $_.Title
$wci.UseSamePermissionsAsParentSite = $true
$wci.WebTemplate = "{D0714A63-356A-4B73-815B-6E1DF824237F}#Template"
$wci.Language = 1033
$blogWeb = $site.RootWeb.Webs.Add($wci);
}