You write a script against a SharePoint list, ask for a column by the name you can see in the browser, and get nothing back. No error, just an empty value.
The column is called Project Status on screen. It is not called that underneath.
Run against a real list on PnP.PowerShell 2.12.0.
Every column has two names
The Title is the label in the browser. Anyone can change it and it can contain anything.
The InternalName is the key. It is set once, when the column is created, and it never changes again.
Here is the difference in one line:
$item = Get-PnPListItem -List 'Projects' -Id 1
$item['ProjectStatus'] # internal name
$item['Project Status'] # display name
By internal name 'ProjectStatus' : 'Complete'
By display name 'Project Status' : ''
An empty string, not an error. That is why this is so easy to lose an hour to – the script runs, it just quietly does nothing.
Getting the list
The obvious command gives you far more than you want:
Get-PnPField -List 'Projects'
On a list where I had created exactly five columns:
Total fields : 90
Hidden : 61
Inherited from base : 83
Actually yours : 5
Ninety. So filter it down to the ones you actually made:
Get-PnPField -List 'Projects' |
Where-Object { -not $_.FromBaseType -and -not $_.Hidden } |
Select-Object Title, InternalName, TypeAsString |
Format-Table -AutoSize
Title InternalName TypeAsString
----- ------------ ------------
Project Status ProjectStatus Choice
Project Owner ProjectOwner User
Due Date DueDate DateTime
Budget Budget Currency
Notes Notes Note
FromBaseType is the useful one. It is true for everything SharePoint gave you and false for everything you added.
What happens to spaces
Those internal names are clean because they were created by script. Columns created in the browser are not, because SharePoint derives the internal name from whatever you typed.
I made one with a space on purpose:
Add-PnPField -List 'Projects' -DisplayName 'Demo Review Date' `
-InternalName 'Demo Review Date' -Type DateTime
Title : Demo Review Date
InternalName : Demo_x0020_Review_x0020_Date
StaticName : Demo_x0020_Review_x0020_Date
Every space becomes _x0020_. A column called “Date of Last Review” ends up as Date_x0020_of_x0020_Last_x0020_Review, and that is what your script has to use.
This is also why you should create columns with a short name and rename them afterwards, which brings us to the thing that actually catches people.
Renaming does not change the internal name
I renamed that column and looked again:
Set-PnPField -List 'Projects' -Identity 'Demo Review Date' -Values @{ Title = 'Renamed Review Date' }
Title : Renamed Review Date
InternalName : Demo_x0020_Review_x0020_Date
The label changed. The key did not, and it never will.
So a column that says Review Date today might be Temp_x0020_Column underneath because of what somebody typed two years ago. You cannot work out the internal name by looking at the browser. You have to ask.
The practical version of that: create the column with the internal name you want, then rename it to whatever reads well. Give it ReviewDate first, then change the label to “Date of Last Review”. You get a clean key and a readable heading.
Built-in columns are worse
Some of the ones you use most have internal names with no relationship to the label at all:
Title InternalName TypeAsString
----- ------------ ------------
Attachments Attachments Attachments
Created By Author User
Content Type ContentType Computed
Created Created DateTime
Modified By Editor User
Name FileLeafRef File
URL Path FileRef Lookup
ID ID Counter
Modified Modified DateTime
Title Title Text
Created By is Author. Modified By is Editor. Those two are the ones everybody hits, and there is nothing on screen that hints at it.
In a document library it gets stranger still:
Title InternalName TypeAsString
----- ------------ ------------
Created By Author User
Type DocIcon Computed
Modified By Editor User
File Size File_x0020_Size Lookup
Name FileLeafRef File
URL Path FileRef Lookup
A file’s Name is FileLeafRef. Its path is FileRef. And File Size carries the _x0020_ encoding as a built-in, which tells you Microsoft named it in a browser too.
One thing that will not work at all
I tried to make a column called Cost & Time-Frame to see what an ampersand does:
An error occurred while parsing EntityName. Line 1, position 32.
Not an encoded name – a failure. PnP builds field XML underneath, and a raw ampersand breaks the parse before SharePoint ever sees it. The error mentions XML parsing and does not mention your column, which is not helpful when you are three lines into a provisioning script.
Note that this is a script limitation. The browser will happily create that column and encode it for you. So this is one of the few cases where the UI does something PowerShell will not.
The line worth keeping
Get-PnPField -List 'YourList' |
Where-Object { -not $_.FromBaseType -and -not $_.Hidden } |
Select-Object Title, InternalName, TypeAsString |
Sort-Object Title
Run it before you write anything against a list you did not create yourself. Thirty seconds, and it saves the empty-string hour 🙂