Spring til indholdet

Upload fra et fildrev til SharePoint Online med PowerShell

Et planlagt PnP PowerShell-script, der flytter filer fra et netværksdrev til et dokumentbibliotek, logger hvert trin og kun sletter kilden, når uploaden er gået igennem.

Opdateret PowerShellSharePoint Online 15 min. læsning

Et gammelt system lægger filer på et netværksdrev hver nat — rapporter, eksporter, PDF’er — og de skal ind i et dokumentbibliotek i SharePoint Online. Så længe nogen gør det i hånden hver morgen, bliver filer glemt eller lagt op to gange. Det her script kører som planlagt opgave: det finder filerne på drevet, lægger dem i biblioteket, sletter hver fil fra drevet, når den er oppe, og skriver det hele i en daglig logfil.

Scheduled Task → PowerShell Script → Connect to SharePoint (App Auth) → Scan File Share → Upload Files → Delete Local Copies → Write Logs → Exit

Forbindelsen i dag#

Scriptet herunder logger ind med klient-id og klienthemmelighed (-ClientId og -ClientSecret). I PnP PowerShell er det ACS-login, og Azure ACS holdt op med at virke for SharePoint Online den 2. april 2026. For nye tenants har det været slået fra siden november 2024. Som det står, kan scriptet altså ikke længere forbinde.

Det, der virker, er en app-registrering i Entra ID med et certifikat. Forbindelseslinjen i Get-SharePoint-Connection bliver da:

1$connection = Connect-PnPOnline -Url $global:sharePointSite `
2    -ClientId $global:sharepointClient `
3    -Tenant "<tenant>.onmicrosoft.com" `
4    -Thumbprint "<certifikat-thumbprint>" `
5    -ReturnConnection

-Thumbprint finder certifikatet i Windows’ certifikatlager hos den konto, opgaven kører som. Alternativet er -CertificatePath og -CertificatePassword med en .pfx-fil. $global:sharepointClientSecret bliver så overflødig.

Forudsætninger#

PnP PowerShell. På en maskine med internet:

1# Install for current user
2Install-Module -Name PnP.PowerShell -Scope CurrentUser -Force
3
4# Verify installation
5Get-Module -Name PnP.PowerShell -ListAvailable

På en server uden internet hentes modulet på en anden maskine, kopieres over og importeres fra mappen:

1Save-Module -Name PnP.PowerShell -Path "C:\Temp\PnPModules"
1Import-Module -Name "D:\Scripts\external-modules\PnP.PowerShell" -Verbose

PnP.PowerShell 2.x og nyere kræver PowerShell 7, ikke Windows PowerShell 5.1.

App-registrering i Entra ID.

  1. App registrations → New registration. Navn fx SharePoint File Upload Automation, Single tenant, ingen redirect-URI.
  2. API permissions → Add a permission → SharePoint → Application permissions → Sites.ReadWrite.All, og Grant admin consent.
  3. Certificates & secrets → upload certifikatets offentlige del (.cer). Den private nøgle bliver på serveren.
  4. Notér Application (client) ID og tenant-id’et fra oversigten.

Sites.ReadWrite.All giver adgang til alle websteder i tenanten. Skal appen kun kunne skrive ét sted, er det Sites.Selected i stedet, og så gives adgangen pr. websted:

1Grant-PnPAzureADAppSitePermission -AppId "<klient-id>" -DisplayName "SharePoint File Upload Automation" -Site "https://<tenant>.sharepoint.com/sites/<websted>" -Permissions Write

Biblioteket. Et eksisterende bibliotek eller et nyt, fx “Uploaded Files”. Scriptet skal bruge den serverrelative sti, fx /sites/YourSite/UploadedFiles. Stien er den del af bibliotekets adresse, der kommer efter domænet — ikke visningsnavnet og ikke listedit.aspx?List={GUID}, som bibliotekets indstillinger viser.

Er indholdsgodkendelse slået til (Versioning settings → Content Approval), sættes $global:sharePointModerationEnabled til $true, og scriptet godkender filerne, når de lægges op.

Scriptet#

Upload-FilesToSharePoint.ps1:

  1<#
  2.SYNOPSIS
  3    Automated file upload from network file share to SharePoint Online
  4.DESCRIPTION
  5    Monitors a network file share, uploads new files to SharePoint document library,
  6    deletes successfully uploaded files, and maintains comprehensive logs.
  7.EXAMPLE
  8    .\Upload-FilesToSharePoint.ps1
  9#>
 10
 11#---------------------------------------------------------------------------------------------------
 12# Script Settings
 13#---------------------------------------------------------------------------------------------------
 14
 15# File Share Configuration
 16$global:localFolderPath = "\\fileserver\reports\daily"
 17
 18# SharePoint Configuration
 19$global:sharePointSite = "https://yourtenant.sharepoint.com/sites/yoursite"
 20$global:sharepointServerRelativeDocumentLibraryPath = "/sites/yoursite/UploadedFiles"
 21$global:sharePointModerationEnabled = $false  # Set to $true if content approval enabled
 22
 23# Azure AD App Authentication
 24$global:sharepointClient = "YOUR-CLIENT-ID-HERE"
 25$global:sharepointClientSecret = "YOUR-CLIENT-SECRET-HERE"
 26
 27# Logging Configuration
 28$global:runningFolderPath = Convert-Path "."
 29$global:logPath = ""
 30$global:keepLogFiles = 10  # Retain last 10 days of logs
 31
 32#---------------------------------------------------------------------------------------------------
 33# Auxiliary Methods
 34#---------------------------------------------------------------------------------------------------
 35
 36function Ensure-Structure($date) {
 37    <#
 38    .SYNOPSIS
 39        Creates log directory structure and manages log retention
 40    #>
 41    try {
 42        $global:logPath = "$($global:runningFolderPath)\logs"
 43        New-Item -ItemType Directory -Force -Path $global:logPath | Out-Null
 44
 45        $file = "$($date.ToString("yyyyMMdd")).log"
 46        $global:logFilePath = "$($global:logPath)\$($file)"
 47
 48        # Log rotation - delete old log files
 49        $logFiles = Get-ChildItem $global:logPath | Sort-Object FullName -Descending
 50        if ($logFiles -ne $null -and $logFiles.Length -gt $global:keepLogFiles) {
 51            $exclude = $logFiles | Select-Object -Skip $global:keepLogFiles
 52
 53            foreach ($current in $exclude) {
 54                try {
 55                    Remove-Item -Path $current.FullName -Force
 56                    Write-Verbose "Deleted old log file: $($current.Name)"
 57                }
 58                catch {
 59                    Write-Warning "Failed to delete old log file: $($current.Name)"
 60                }
 61            }
 62        }
 63
 64        return $true
 65    }
 66    catch {
 67        Write-Error "Failed to ensure log structure: $_"
 68        return $false
 69    }
 70}
 71
 72function Log-Entry([string]$message, [string]$type) {
 73    <#
 74    .SYNOPSIS
 75        Writes log entry to file and console with timestamp and severity
 76    #>
 77    try {
 78        if ([string]::IsNullOrEmpty($type)) {
 79            $type = "INFO"
 80        }
 81        else {
 82            $type = $type.ToUpper()
 83        }
 84
 85        $logMessage = "$((Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) :: $($type) :: $($message)"
 86
 87        switch ($type) {
 88            "ERROR" {
 89                Write-Host $logMessage -ForegroundColor Red
 90                Add-Content -Path $global:logFilePath -Value $logMessage
 91            }
 92            "WARNING" {
 93                Write-Host $logMessage -ForegroundColor Yellow
 94                Add-Content -Path $global:logFilePath -Value $logMessage
 95            }
 96            "SUCCESS" {
 97                Write-Host $logMessage -ForegroundColor Green
 98                Add-Content -Path $global:logFilePath -Value $logMessage
 99            }
100            default {
101                Write-Host $logMessage -ForegroundColor White
102                Add-Content -Path $global:logFilePath -Value $logMessage
103            }
104        }
105    }
106    catch {
107        Write-Error "Failed to write log entry: $_"
108    }
109}
110
111#---------------------------------------------------------------------------------------------------
112# SharePoint Methods
113#---------------------------------------------------------------------------------------------------
114
115function Get-SharePoint-Connection {
116    <#
117    .SYNOPSIS
118        Establishes connection to SharePoint Online using app-only authentication
119    #>
120    try {
121        Log-Entry "Connecting to SharePoint Online: $($global:sharePointSite)" "INFO"
122
123        $connection = Connect-PnPOnline -Url $global:sharePointSite `
124            -ClientId $global:sharepointClient `
125            -ClientSecret $global:sharepointClientSecret `
126            -ReturnConnection
127
128        $web = Get-PnPWeb -Connection $connection
129
130        if ($web -ne $null) {
131            Log-Entry "Connected successfully to: $($web.Url)" "SUCCESS"
132            return $connection
133        }
134        else {
135            Log-Entry "Connection failed - web object is null" "WARNING"
136            Disconnect-PnPOnline
137            return $null
138        }
139    }
140    catch {
141        Log-Entry "SharePoint connection error: $($_.Exception.Message)" "ERROR"
142        return $null
143    }
144}
145
146function Upload-File-To-SharePoint($connection, $filePath, $fileName) {
147    <#
148    .SYNOPSIS
149        Uploads a single file to SharePoint document library
150    #>
151    try {
152        if ($global:sharePointModerationEnabled -eq $true) {
153            # Upload and auto-approve if moderation enabled
154            Add-PnPFile -Path $filePath `
155                -Folder $global:sharepointServerRelativeDocumentLibraryPath `
156                -Publish `
157                -PublishComment "Auto-uploaded by scheduled task" `
158                -Connection $connection | Out-Null
159        }
160        else {
161            # Standard upload
162            Add-PnPFile -Path $filePath `
163                -Folder $global:sharepointServerRelativeDocumentLibraryPath `
164                -Connection $connection | Out-Null
165        }
166
167        Log-Entry "Upload successful: $fileName" "SUCCESS"
168        return $true
169    }
170    catch {
171        Log-Entry "Upload failed for $fileName - $($_.Exception.Message)" "ERROR"
172        return $false
173    }
174}
175
176#---------------------------------------------------------------------------------------------------
177# File Operations
178#---------------------------------------------------------------------------------------------------
179
180function Get-Files-To-Upload($folderPath) {
181    <#
182    .SYNOPSIS
183        Scans local folder and returns list of files to upload
184    #>
185    try {
186        $array = @()
187        Log-Entry "Scanning folder: $folderPath" "INFO"
188
189        if (-not (Test-Path $folderPath)) {
190            Log-Entry "Folder path does not exist: $folderPath" "WARNING"
191            return $array
192        }
193
194        $files = Get-ChildItem -Path $folderPath -File
195
196        if ($files.Count -eq 0) {
197            Log-Entry "No files found in folder" "INFO"
198            return $array
199        }
200
201        foreach ($file in $files) {
202            $data = [PSCustomObject]@{
203                FullPath = $file.FullName
204                Name     = $file.Name
205            }
206            $array += $data
207        }
208
209        Log-Entry "Found $($array.Count) file(s) to upload" "INFO"
210        return $array
211    }
212    catch {
213        Log-Entry "Error scanning folder: $($_.Exception.Message)" "ERROR"
214        return @()
215    }
216}
217
218function Delete-Local-File($filePath) {
219    <#
220    .SYNOPSIS
221        Deletes file from local file share after successful upload
222    #>
223    try {
224        Remove-Item -Path $filePath -Force
225        Log-Entry "Deleted local file: $(Split-Path $filePath -Leaf)" "SUCCESS"
226        return $true
227    }
228    catch {
229        Log-Entry "Failed to delete: $(Split-Path $filePath -Leaf) - $($_.Exception.Message)" "ERROR"
230        return $false
231    }
232}
233
234function Upload-Files-To-SharePoint-And-Delete-Local($connection, $fileInfos) {
235    <#
236    .SYNOPSIS
237        Uploads multiple files and deletes them after successful upload
238    #>
239    try {
240        $successCount = 0
241        $failCount = 0
242
243        foreach ($fileInfo in $fileInfos) {
244            Log-Entry "Processing: $($fileInfo.Name)" "INFO"
245
246            $uploadSuccess = Upload-File-To-SharePoint $connection $fileInfo.FullPath $fileInfo.Name
247
248            if ($uploadSuccess -eq $true) {
249                $deleteSuccess = Delete-Local-File $fileInfo.FullPath
250                if ($deleteSuccess) {
251                    $successCount++
252                }
253            }
254            else {
255                $failCount++
256            }
257        }
258
259        Log-Entry "Upload summary: $successCount successful, $failCount failed" "INFO"
260    }
261    catch {
262        Log-Entry "Batch upload error: $($_.Exception.Message)" "ERROR"
263    }
264}
265
266#---------------------------------------------------------------------------------------------------
267# Main Process
268#---------------------------------------------------------------------------------------------------
269
270function Start-Process {
271    <#
272    .SYNOPSIS
273        Main orchestration function
274    #>
275    try {
276        Log-Entry "-" * 100 "INFO"
277        Log-Entry "Upload process started" "INFO"
278        Log-Entry "Source: $($global:localFolderPath)" "INFO"
279        Log-Entry "Destination: $($global:sharePointSite)$($global:sharepointServerRelativeDocumentLibraryPath)" "INFO"
280
281        $fileInfos = Get-Files-To-Upload $global:localFolderPath
282
283        if ($fileInfos.Count -gt 0) {
284            $connection = Get-SharePoint-Connection
285
286            if ($connection -eq $null) {
287                Log-Entry "Cannot proceed without SharePoint connection" "ERROR"
288                return $false
289            }
290
291            Upload-Files-To-SharePoint-And-Delete-Local $connection $fileInfos
292
293            Disconnect-PnPOnline -Connection $connection
294        }
295        else {
296            Log-Entry "No files to process" "INFO"
297        }
298
299        return $true
300    }
301    catch {
302        Log-Entry "Process error: $($_.Exception.Message)" "ERROR"
303        return $false
304    }
305    finally {
306        Log-Entry "Upload process completed" "INFO"
307        Log-Entry "-" * 100 "INFO"
308    }
309}
310
311#---------------------------------------------------------------------------------------------------
312# Execution
313#---------------------------------------------------------------------------------------------------
314
315$ensured = Ensure-Structure (Get-Date)
316if ($ensured -eq $false) {
317    Write-Error "Failed to ensure log structure, exiting"
318    exit 1
319}
320
321$result = Start-Process
322if ($result -eq $false) {
323    Write-Error "Upload process failed, check logs at: $($global:logFilePath)"
324    exit 1
325}
326
327Write-Host "Process completed successfully" -ForegroundColor Green
328exit 0

Opsætning#

Variablerne øverst i scriptet:

1$global:localFolderPath = "\\fileserver\reports\daily"
1$global:sharePointSite = "https://contoso.sharepoint.com/sites/ProjectSite"
1$global:sharepointServerRelativeDocumentLibraryPath = "/sites/ProjectSite/Documents"
1$global:sharepointClient = "<klient-id>"  # Your Client ID
2$global:sharepointClientSecret = "YOUR-SECRET-HERE"  # Your Client Secret
1$global:sharePointModerationEnabled = $false  # Set to $true if library uses content approval

Kør det i hånden først:

1# Navigate to script directory
2cd "D:\Scripts\SharePointUpload"
3
4# Run script
5.\Upload-FilesToSharePoint.ps1
6
7# Check log output
8Get-Content ".\logs\$(Get-Date -Format 'yyyyMMdd').log"

Loggen ser sådan ud, når det går:

2025-10-03 14:30:15 :: INFO :: Connecting to SharePoint Online: https://tenant.sharepoint.com/sites/site
2025-10-03 14:30:18 :: SUCCESS :: Connected successfully to: https://tenant.sharepoint.com/sites/site
2025-10-03 14:30:18 :: INFO :: Scanning folder: \\fileserver\reports\daily
2025-10-03 14:30:18 :: INFO :: Found 5 file(s) to upload
2025-10-03 14:30:19 :: SUCCESS :: Upload successful: report_20251003.pdf
2025-10-03 14:30:19 :: SUCCESS :: Deleted local file: report_20251003.pdf
...
2025-10-03 14:30:25 :: INFO :: Upload summary: 5 successful, 0 failed

Jeg prøver med én fil, så fem, så en hel nats portion.

Planlagt opgave#

I Task Scheduler → Create Basic Task: navn SharePoint File Upload, dagligt kl. 2, Start a program.

powershell.exe
-ExecutionPolicy Bypass -File "D:\Scripts\SharePointUpload\Upload-FilesToSharePoint.ps1"
D:\Scripts\SharePointUpload

Det er program, argumenter og Start in. Med PnP.PowerShell 2.x og nyere skal programmet være pwsh.exe, ikke powershell.exe. Opgaven skal køre whether user is logged on or not, med highest privileges, som en servicekonto, der både kan læse og slette på drevet og har certifikatet i sit lager.

1# Run task immediately to test
2Start-ScheduledTask -TaskName "SharePoint File Upload"
3
4# Check task status
5Get-ScheduledTask -TaskName "SharePoint File Upload" | Select-Object State, LastRunTime, LastTaskResult
6
7# View task history
8Get-ScheduledTaskInfo -TaskName "SharePoint File Upload"

Varianter#

Undermapper pr. måned. Filerne lægges i 2025-10/, 2025-11/ osv., og mappen oprettes, hvis den mangler:

 1function Upload-File-To-SharePoint($connection, $filePath, $fileName) {
 2    try {
 3        # Create date-based subfolder
 4        $dateFolder = (Get-Date).ToString("yyyy-MM")
 5        $targetFolder = "$($global:sharepointServerRelativeDocumentLibraryPath)/$dateFolder"
 6        
 7        # Ensure folder exists
 8        Resolve-PnPFolder -SiteRelativePath $targetFolder -Connection $connection | Out-Null
 9        
10        # Upload to subfolder
11        Add-PnPFile -Path $filePath `
12            -Folder $targetFolder `
13            -Connection $connection | Out-Null
14
15        Log-Entry "Uploaded to $dateFolder folder: $fileName" "SUCCESS"
16        return $true
17    }
18    catch {
19        Log-Entry "Upload failed: $($_.Exception.Message)" "ERROR"
20        return $false
21    }
22}

Karantæne for fejlede filer. En fil, der ikke kom op, flyttes til en fejlmappe i stedet for at blive liggende og blive forsøgt igen hver nat:

 1$global:errorFolderPath = "\\fileserver\reports\upload-errors"
 2
 3function Upload-Files-To-SharePoint-And-Delete-Local($connection, $fileInfos) {
 4    foreach ($fileInfo in $fileInfos) {
 5        $uploadSuccess = Upload-File-To-SharePoint $connection $fileInfo.FullPath $fileInfo.Name
 6
 7        if ($uploadSuccess -eq $true) {
 8            Delete-Local-File $fileInfo.FullPath
 9        }
10        else {
11            # Move to error folder for manual review
12            $errorPath = Join-Path $global:errorFolderPath $fileInfo.Name
13            Move-Item -Path $fileInfo.FullPath -Destination $errorPath -Force
14            Log-Entry "Moved failed file to error folder: $($fileInfo.Name)" "WARNING"
15        }
16    }
17}

Kun bestemte filtyper:

 1function Get-Files-To-Upload($folderPath) {
 2    $allowedExtensions = @(".pdf", ".docx", ".xlsx")
 3    $array = @()
 4
 5    $files = Get-ChildItem -Path $folderPath -File | Where-Object {
 6        $allowedExtensions -contains $_.Extension
 7    }
 8
 9    foreach ($file in $files) {
10        $data = [PSCustomObject]@{
11            FullPath = $file.FullName
12            Name     = $file.Name
13        }
14        $array += $data
15    }
16
17    Log-Entry "Found $($array.Count) eligible file(s) (filtered by extension)" "INFO"
18    return $array
19}

Metadata på filen. Kræver kolonnerne UploadSource (tekst), ProcessedDate (dato og tid) og SourceLocation (tekst) i biblioteket:

 1function Upload-File-To-SharePoint($connection, $filePath, $fileName) {
 2    try {
 3        # Upload file
 4        $uploadedFile = Add-PnPFile -Path $filePath `
 5            -Folder $global:sharepointServerRelativeDocumentLibraryPath `
 6            -Connection $connection
 7
 8        # Set metadata
 9        Set-PnPListItem -List "Documents" `
10            -Identity $uploadedFile.ListItemAllFields.Id `
11            -Values @{
12                "UploadSource" = "Automated Script"
13                "ProcessedDate" = (Get-Date)
14                "SourceLocation" = $global:localFolderPath
15            } `
16            -Connection $connection
17
18        Log-Entry "Upload and metadata set: $fileName" "SUCCESS"
19        return $true
20    }
21    catch {
22        Log-Entry "Upload/metadata error: $($_.Exception.Message)" "ERROR"
23        return $false
24    }
25}

-List "Documents" skal være det bibliotek, der uploades til — i eksemplet her er det ikke. Add-PnPFile har også selv en -Values-parameter, så metadata kan sættes i samme kald.

Revisionsspor. En CSV-linje pr. fil ved siden af loggen:

 1function Log-GDPR-Audit($fileName, $action) {
 2    $auditEntry = [PSCustomObject]@{
 3        Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
 4        FileName = $fileName
 5        Action = $action
 6        ProcessedBy = $env:COMPUTERNAME
 7        SourcePath = $global:localFolderPath
 8    }
 9    
10    $auditPath = "$($global:runningFolderPath)\audit-trail.csv"
11    $auditEntry | Export-Csv -Path $auditPath -Append -NoTypeInformation
12}
13
14# Call in upload function
15Log-GDPR-Audit $fileName "Uploaded to SharePoint and deleted from file share"

Flyt til arkiv i stedet for at slette, når kontoen ikke må slette på drevet:

1function Archive-Local-File($filePath) {
2    $archivePath = "\\fileserver\reports\archive"
3    $fileName = Split-Path $filePath -Leaf
4    Move-Item -Path $filePath -Destination (Join-Path $archivePath $fileName) -Force
5    Log-Entry "Moved to archive: $fileName" "SUCCESS"
6}

Mange filer. I portioner af 100 med en pause imellem, så SharePoint ikke begynder at svare 429:

1# Process files in batches of 100
2$batchSize = 100
3for ($i = 0; $i -lt $fileInfos.Count; $i += $batchSize) {
4    $batch = $fileInfos[$i..([Math]::Min($i + $batchSize - 1, $fileInfos.Count - 1))]
5    Upload-Files-To-SharePoint-And-Delete-Local $connection $batch
6    Start-Sleep -Seconds 5  # Throttle protection
7}

Besked, når det er kørt:

1function Send-Completion-Email($successCount, $failCount) {
2    Send-MailMessage -From "noreply@company.com" `
3        -To "it@company.com" `
4        -Subject "SharePoint Upload: $successCount succeeded, $failCount failed" `
5        -Body (Get-Content $global:logFilePath | Out-String) `
6        -SmtpServer "smtp.company.com"
7}
1Write-EventLog -LogName Application `
2    -Source "SharePoint Upload" `
3    -EventId 1001 `
4    -EntryType Information `
5    -Message "Uploaded $successCount files successfully"

Send-MailMessage er forældet og kan ikke logge ind mod Exchange Online med moderne godkendelse. Write-EventLog findes kun i Windows PowerShell 5.1, og kilden skal oprettes én gang med New-EventLog, før den kan bruges.

Fejlfinding#

“Cannot bind argument to parameter ‘Path’ because it is null”. Drevet kan ikke nås eller er tomt. Prøv som servicekontoen:

1# Test file share access
2Test-Path "\\fileserver\reports\daily"
3
4# Check permissions
5Get-Acl "\\fileserver\reports\daily" | Format-List
6
7# Test as service account
8runas /user:DOMAIN\ServiceAccount powershell.exe
9Test-Path "\\fileserver\reports\daily"

“AADSTS7000215: Invalid client secret provided”. Hemmeligheden er udløbet eller forkert. Med certifikat er det tilsvarende et udløbet certifikat — datoen står under Certificates & secrets.

“Access denied. You do not have permission”. Tilladelsen mangler, eller admin-samtykket er ikke givet. Virker det med en almindelig bruger, er det appens rettigheder, der er problemet:

1Connect-PnPOnline -Url $siteUrl -Interactive
2Add-PnPFile -Path $file -Folder $folder

-Interactive kræver i dag også en app-registrering (-ClientId), fordi PnP’s egen fælles app er fjernet. Den gamle vej med appinv.aspx og AllowAppOnlyPolicy er ACS og virker ikke længere. Rettigheder til ét websted gives med Sites.Selected som ovenfor.

“Cannot find path ‘\fileserver\reports’”. UNC-stien kan ikke nås fra opgavens kontekst. Et drevbogstav i scriptet eller et eksplicit login:

1New-PSDrive -Name "Z" -PSProvider FileSystem -Root "\\fileserver\reports" -Persist
2$global:localFolderPath = "Z:\daily"
1$netCred = New-Object System.Management.Automation.PSCredential("DOMAIN\User", (ConvertTo-SecureString "Password" -AsPlainText -Force))
2New-PSDrive -Name "Z" -PSProvider FileSystem -Root "\\fileserver\reports" -Credential $netCred

Bedst er det dog at lade opgaven køre som en konto, der har adgang til drevet, så der ikke står en adgangskode i scriptet.

Filerne kommer op, men bliver ikke slettet. Kontoen har ikke Delete eller Modify på drevet:

1# Check delete permissions
2$acl = Get-Acl "\\fileserver\reports\daily"
3$acl.Access | Where-Object { $_.IdentityReference -eq "DOMAIN\ServiceAccount" }
4
5# Verify required permission:
6# FileSystemRights should include "Delete" or "Modify"

Mappen i SharePoint findes ikke. Den serverrelative sti er forkert:

1# Test folder existence
2$folderPath = "/sites/sitename/LibraryName"
3Get-PnPFolder -Url $folderPath

Det der driller#

  • Klienthemmeligheden er ACS, og ACS er væk (se øverst). Scriptet skal have et certifikat for at forbinde i dag.
  • Log-Entry "-" * 100 "INFO" skriver ikke en streg. PowerShell læser argumenterne som "-", *, 100 og "INFO", så beskeden bliver - og typen *. Det skal være Log-Entry ("-" * 100) "INFO".
  • Funktionen Start-Process skygger for PowerShells egen cmdlet af samme navn. Det virker i scriptet, men et andet navn, fx Start-Upload, undgår overraskelser.
  • Filerne slettes kun efter en upload, der gik igennem, men en fil, der har samme navn som en fil i biblioteket, overskrives uden videre. Add-PnPFile spørger ikke.
  • Eksemplerne i originalen havde to ting, der ikke virker og er udeladt her: Add-PnPFile -ChunkSize findes ikke, og ForEach-Object -Parallel kan ikke se scriptets funktioner, fordi hver tråd har sin egen runspace.
  • Loggen roterer på antal filer, ikke på dage. $global:keepLogFiles = 10 er de ti nyeste logfiler, og der kommer kun en fil på de dage, scriptet har kørt.
  • Hemmeligheder hører ikke til i scriptet. Certifikatet ligger i servicekontoens lager. Skal der alligevel bruges en hemmelighed, ligger den i Azure Key Vault eller Windows Credential Manager.

Alle noter