F# managing references when compiling from fsx script - sharepoint

I need to interact with SharePoint (on premise) and decided to give a try to F#. It is or should be simple enough that I did it with CLI tools only.
I managed to interact with a site and get the info I needed. I struggled with the required DLL, but in the end
#if INTERACTIVE
#r #"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
#r #"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
// seems to be required
#r #"[...]\Microsoft.Online.SharePoint.Client.Tenant.dll.15.0.4615.1001\Microsoft.Online.SharePoint.Client.Tenant.dll"
#r #"[...]\SharePointPnPCoreOnline.3.8.1904\lib\net45\OfficeDevPnP.Core.dll"
#endif
worked out with Fsi REPL or Fsi script.fsx, but I cannot make it compile, wether as a fs file or with the fsx script.
My code is, say:
open Microsoft.SharePoint.Client;;
let main () =
let authnManager = OfficeDevPnP.Core.AuthenticationManager()
printfn "%A" authnManager
0
main()
Running with fsi:
PS> fsi script.fsx
OfficeDevPnP.Core.AuthenticationManager #OK!
Trying to compile:
PowerShell> fsc --warn:5 -r "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll" `
>> -r "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll" `
>> -r "absolute\path\to\Microsoft.Online.SharePoint.Client.Tenant.dll.15.0.4615.1001\Microsoft.Online.SharePoint.Client.Tenant.dll" `
>> -r "absolute\path\to\SharePointPnPCoreOnline.3.8.1904\lib\net45\OfficeDevPnP.Core.dll" .\script.fsx
Microsoft (R) F# Compiler version 10.4.0 for F# 4.6
Copyright (c) Microsoft Corporation. All Rights Reserved.
> .\script.exe
Exception non gérée (unmanaged exception) : System.IO.FileNotFoundException:
Impossible de charger le fichier ou l'assembly 'OfficeDevPnP.Core,
Version=3.8.1904.0, Culture=neutral, PublicKeyToken=5e633289e95c321a'
ou une de ses dépendances. Le fichier spécifié est introuvable.
à Script.main()
à <StartupCode$script>.$Script$fsx.main#()
Why this difference? What am I missing? How to load references with fsc (as nuget installed quite a few transitive dependencies) ? They must be managed as well with fsc as with fsi, for sure! (Unless there's a specific issue with OfficeDevPnP.Core.dll...)

I think the DLLs referenced via #r in F# Interactive need to be in dependency-order. So, if the SharePoint DLLs depend on the OfficeDevPnp DLL, then the OfficeDevPnp DLL needs to be referenced first (it's #r line needs to come before the SharePoint #r lines). You will have to reset the interactive session once you've loaded the DLLs to re-load them in the correct order.
In general, loading package dependencies in F# interactive is quite tricky. There are some F# tools like Paket you can take a look at that might make your life a little easier. Another option, if you already have a Visual Studio project with the references you need, is to use that to generate the package references for your script file. You can read the .fsproj file and generate the #r statements from the references used by the project. Something like this might work:
#r "System.Xml"
#r "System.Xml.Linq"
open System
open System.IO
open System.Linq
open System.Xml.Linq
let inline isNotNull o = o |> isNull |> not
let private getProject path =
Directory.EnumerateFiles(path, "*.*proj") |> Seq.head |> XDocument.Load
let generateDlls path =
let projectFile = getProject path
let references =
projectFile.Descendants <| XName.Get("HintPath", "http://schemas.microsoft.com/developer/msbuild/2003")
|> Seq.filter (fun reference -> reference.Value.ToLower().EndsWith(".dll"))
|> Seq.filter (fun reference -> reference.Value.StartsWith("$") |> not)
|> Seq.map (fun reference -> reference.Value)
let projects =
projectFile.Descendants <| XName.Get("ProjectReference", "http://schemas.microsoft.com/developer/msbuild/2003")
|> Seq.map (fun reference -> reference.Elements(XName.Get("Name", "http://schemas.microsoft.com/developer/msbuild/2003")).SingleOrDefault())
|> Seq.filter (fun nameElement -> nameElement |> isNotNull)
|> Seq.map (fun nameElement -> nameElement.Value)
|> Seq.map (fun reference -> sprintf "../%s/bin/debug/%s.dll" reference reference)
references
|> Seq.append projects
|> Seq.iter (fun reference -> printfn "#r #\"%s\"" reference)

Related

The type or namespace name "ContentBySearchWebPart" could not be found

Recently, I was trying to add a Content Search Web Part using csom in Visual Studio 2017, While I was adding the "ContentBySearchWebPart" class, an error occured as:
"The type or namespace name "ContentBySearchWebPart" could not be found(you are missing a using directive or an assembly reference?)".
Even I was using the namespace as "Microsoft.Office.Server.Search.WebControls" and also the "Microsoft.SharePoint.Client.Search" dll.
How can I resolve this issue?
You need to add reference "Microsoft.Office.Server.Search.dll" manually from ISAPI folder or GAC
ISAPI folder
<Windows>\Program Files\Common Files\Microsoft Shared\Web Server Extenstions\16\ISAPI
GAC
(C:\windows\Windows.NET\assembly\GAC_MSIL\Microsoft.Office.Server.Search).
Namespace: Microsoft.Office.Server.Search.WebControls
Assembly: Microsoft.Office.Server.Search (in Microsoft.Office.Server.Search.dll)
https://social.msdn.microsoft.com/Forums/sharepoint/en-US/c6f03ea3-f893-4126-88e3-52a2a66b5f18/missing-assembly-reference-for-microsoftofficeserversearch?forum=sharepointdevelopmentprevious
Check your \Program Files\Common Files\Microsoft Shared\Web Server Extenstions\14\ISAPI, you'll find alot more DLL's inside this folder including your Microsoft.Office.Server.Search.dll that you are looking for.

Create lnk shortcut from lua (without lfs)

I would like to write a function to create a windows .lnk file from my lua script. I found a function in the LuaFileSystem library . Is there a way to do this without the library? (The reason: I am writing the script for multiple users, would be nice if we don't have to install the library on every machine.)
I appreciate the help!
To make a shortcut (an .lnk file)
-- your .lnk file
local your_shortcut_name = "your_shortcut.lnk"
-- target (file or folder) with full path
local your_target_filespec = [[C:\Windows\notepad.exe]]
local ps = io.popen("powershell -command -", "w")
ps:write("$ws = New-Object -ComObject WScript.Shell;$s = $ws.CreateShortcut('"..your_shortcut_name.."');$s.TargetPath = '"..your_target_filespec.."';$s.Save()")
ps:close()
To make a symlink simply use os.execute"mklink ..."
Use luacom is faster than powershell
local luacom=require'luacom'
local shortcut_file_path='test_create_shortcut.lnk'
local target_file_path=arg[0]
local shellObject=luacom.CreateObject("WScript.Shell")
local shortcut=shellObject:CreateShortcut(shortcut_file_path)
shortcut.TargetPath=target_file_path
shortcut:Save()
assert(io.open(shortcut_file_path)):close()--shortcut file exist now
os.remove(shortcut_file_path)
And use FileSystemObject object (another COM), or Windows shell link file format spec for Kaitai Struct (parse binary file struct to get info on various file format) to retrieve shortcut info. Which 'lfs' can't do now.
see: Create a desktop shortcut with Windows Script Host - Windows Client | Microsoft Docs
LuaCOM User Manual (Version 1.3)

Strong Named satellite assemblies using ResGen & AL with multiple resx / resources files

For whatever reason, when compiling multiple .resources files into a single satellite assembly (.dll) the resources will not show. Compiling a single resource will work. Here are the steps I used...
We have a project called "Report Viewer". This project is signed using a key, MySnKey.snk via Properties > Signing > Sign the assembly , choose a strong name key file: MySnKey.snk.
All forms have been updated to Localizable = True
We processed all the resx files into German de-DE. There are only two resx: MainForm.resx (project root directory) and Resources.resx (Properties directory).
I have a folder with MainForm.de-DE.resx and Resources.de-DE.resx which are the translated versions of these files.
Using resgen,
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" /compile Resources.de-DE.resx ReportViewer.Resources.de-DE.resources
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" /compile MainForm.de-DE.resx ReportViewer.MainForm.de-DE.resources
This creates the appropriate .resources files. Now to link, I use AL.exe:
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\AL" /t:lib /embed:ReportViewer.MainForm.de-DE.resources,ReportViewer.Resources.de-DE.resources /culture:de-DE /out:ReportViewer.resources.dll /template:../../../Output/ReportViewer/bin/Debug/ReportViewer.exe /keyfile:../MySnKey.snk
This creates a ReportViewer.resources.dll file. If I place it into the appropriate sub folder de-DE>ReportViewer.resources.dll, no luck. In Program.cs, before Run is called, I have
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
IF, I only include the MainForm like
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\AL" /t:lib /embed:ReportViewer.MainForm.de-DE.resources /culture:de-DE /out:ReportViewer.resources.dll /template:../../../Output/ReportViewer/bin/Debug/ReportViewer.exe /keyfile:../MySnKey.snk
The appropriate resources are displayed.
This is quite confusing to me. If I add the .resx files to the solution, and compile, the output dll works correctly, but we are trying to avoid having to bring these satellite translations into the solution.
No errors are generated and the files are created so I'm lost as to what I'm not doing.
Any help is greatly appreciated. I compared the differences between the output dll when adding the files to the solution to the output file when running AL.exe and they both contain all the translations.
EDIT to include solution since I can't answer the question
Apparently I got confused with the embed option and it's usage. To properly use the AL.exe utility, I had to use:
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" Resources.de-DE.resx ReportViewer.Properties.Resources.de-DE.resources
Notice above that the Properties namespace is added. I had not done that before.
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" MainForm.de-DE.resx ReportViewer.MainForm.de-DE.resources
Notice that no Properties namespace is added since MainForm is Simply in the ReportViewer namespace.
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\AL"
/t:lib
/embed:ReportViewer.Properties.Resources.de-DE.resources
/embed:ReportViewer.MainForm.de-DE.resources
/culture:de-DE /out:ReportViewer.resources.dll /template:../../../Output/ReportViewer/bin/Debug/ReportViewer.exe /keyfile:../MySnKey.snk
Notice that there are multiple /embed items in this line. I was using the comma, which renames the internals and is not what I wanted. Providing a space between the files gives a (seemingly) unrelated error. See http://ondotnet.com/pub/a/dotnet/2002/10/14/local2.htm?page=2 for a great article.
For completeness, another way of writing this script would be
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" Resources.de-DE.resx
notice that no renaming is done here, the output is simply Resources.de-DE.resources
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ResGen" MainForm.de-DE.resx
notice that no renaming is done here, the output is simply MainForm.de-DE.resources
> "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\AL" /t:lib
/embed:Resources.de-DE.resources,ReportViewer.Properties.Resources.de-DE.resources
/embed:MainForm.de-DE.resources,ReportViewer.MainForm.de-DE.resources
/culture:de-DE
/out:ReportViewer.resources.dll /template:../../../Output/ReportViewer/bin/Debug/ReportViewer.exe /keyfile:../MySnKey.snk
Notice that the renaming is done following the comma in the embed option. The first argument is the file name (the .resources file) and after the comma is the fully qualified name (namespace.class.xx-XX.resources).
Answered in the post before I knew I could answer it here.

SharePoint script fails when run as a Visual Studio post-deployment command

I have written a script that inserts some test data into a document library. I intend to use it as a post-deployment step in Visual Studio 2010, so that the library is not empty after a retract & deploy.
The relevant portions of the script are:
Install.ps1:
$scriptDirectory = Split-Path -Path $script:MyInvocation.MyCommand.Path -Parent
. "$scriptDirectory\Include.ps1"
$webUrl = "http://localhost/the_site_name"
$web = Get-SPWeb($webUrl)
...
Include.ps1:
function global:Get-SPSite($url)
{
return new-Object Microsoft.SharePoint.SPSite($url)
}
function global:Get-SPWeb($url,$site)
{
if($site -ne $null -and $url -ne $null){"Url OR Site can be given"; return}
#if SPSite is not given, we have to get it...
if($site -eq $null){
$site = Get-SPSite($url);
...
}
It works fine when run as follows from the command line, even immediately after a Visual Studio re-deploy:
powershell \source\ProjectFiles\TestData\Install.ps1
However, it does not work when I use the exact same command as a post-deployment command line in the SharePoint project's properties in Visual Studio:
Run Post-Deployment Command:
New-Object : Exception calling ".ctor" with "1" argument(s): "The Web applicati
on at http://localhost/the_site_name could not be found. Verify that you have t
yped the URL correctly. If the URL should be serving existing content, the syst
em administrator may need to add a new request URL mapping to the intended appl
ication."
At C:\source\ProjectFiles\TestData\Include.ps1:15 char:18
+ return new-Object <<<< Microsoft.SharePoint.SPSite($url)
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvoca
tionException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.Power
Shell.Commands.NewObjectCommand
Interestingly, I can reproduce the error on the command line if I run:
c:\windows\Syswow64\WindowsPowerShell\v1.0\powershell \source\ProjectFiles\TestData\Install.ps1
However, the post-deployment command fails even if I explicitly run \windows\System32\WindowsPowerShell\v1.0\powershell and \windows\Syswow64\WindowsPowerShell\v1.0\powershell.
Update: Solution found
I seem to be having a similar problem to the one discussed here:
http://social.technet.microsoft.com/Forums/en-US/sharepoint2010programming/thread/faa25866-330b-4e60-8eee-bd72dc9fa5be
I cannot access a 64-bit SharePoint API using 32-bit clients. Because Visual Studio is 32-bit, the post-deployment action will run in a 32-bit process and will fail. There is, however, a 64-bit MSBuild. If we let it run the PowerShell script, all is fine.
Wrap the script in an MSBuild file such as this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Install" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Install">
<Exec Command="powershell .\Install" />
</Target>
</Project>
Then, set the post-deployment command line to:
%WinDir%\Microsoft.NET\Framework64\v4.0.30319\MSBuild $(SolutionDir)\ProjectFiles\TestData\Install.msbuild
Use
%WINDIR%\SysNative\WindowsPowerShell\v1.0\powershell.exe
It’s important that you use the virtual path of %WINDIR%\SysNative and not the actual
path of C:\Windows\System32. The reason for this is that Visual Studio 2010 is a 32-bit
application that needs to call the 64-bit version of powershell.exe to successfully load the
Microsoft.SharePoint.Powershell snap-in.
(c)"Inside Microsoft SharePoint 2010", Microsoft Press, Mar 2011
I had same situation, I needed the Post Deployment powershell script to create dummy data for lists on my local instance. I tried several other ways even using the MSBuild with the .msbuild file as suggested above, but i could not all the variables and had to hard code the file with path and url, this is not what i wanted.
I finally figured out a way to explicitly calling the 64-Bit powershell.exe
I know the 64-bit file has to be there on hard dirve. I know that WinSXS folder has all the files. So quick search for powershell.exe in C:\Windows\winsxs folder i got two files so i grabbed the path for one in amd64 folder.
This is what i have as command in post deployment option
C:\Windows\winsxs\amd64_microsoft-windows-powershell-exe_31bf3856ad364e35_6.1.7600.16385_none_c50af05b1be3aa2b\powershell.exe -command "&{$(ProjectDir)PowerShell\dataload.ps1 -xmlPath "$(ProjectDir)PowerShell\dataload.xml" -webUrl "$(SharePointSiteUrl)"}"
I hope this will help someone in future.
Visual Studio is a 32-bit application, so in 64-bit Windows it runs in a simulated 32-bit environment.
Strangely, the 32-bit environment is called "WoW64" (when 32-bit Windows did this for 16-bit apps, it was called "WoW16". The "WoW" part means "Windows on Windows".
It's similarly strange that "System32" didn't become "System64" with 64-bit Windows. The "32" is from the 16-bit -> 32-bit transition, to differentiate from "System". Whatever, that's legacy/compatibility for you.
In WoW64, everything looks like a 32-bit Windows.
For example, c:\windows\system32 just points to c:\windows\syswow64. 32-bit applications can't (easily) reach anything 64-bit.
It is possible to use PowerShell Remoting to get a 64-bit PowerShell session from a 32-bit environment.
PS>gci env:PROCESSOR_ARCH*
Name Value
---- -----
PROCESSOR_ARCHITECTURE x86
PROCESSOR_ARCHITEW6432 AMD64
PS>Invoke-Command -ConfigurationName Microsoft.PowerShell -ComputerName LOCALHOST { gci env:PROCESSOR_ARCH* }
Name Value PSComputerName
---- ----- --------------
PROCESSOR_ARCHITECTURE AMD64 localhost
I have success doing this as a post deployment command:
%comspec% /c powershell -File "c:\foo\bar.ps1"

Assembly Versioning using CruiseControl.net

I have setup CruiseControl.net for a bunch of my projects which are related.
As a result a single project tag in CruiseControl has multiple SVN checkouts and then a bunch of msbuild tasks compile all the individual sln files.
I need to update the assembly version of all the solutions when this build is being done.
However, since i'm not using nant and not using MSBuild proj files, I am unsure on how to get this.
I wonder if I'm missing something obvious. I just need a solution which can be implemented by making appropriate changes in the ccnet.config file without requiring me to make changes to csproj files.
Thanks,
Anj
What about using a shared AssemblyInfo across your projects?
This is what we do for our products:
Each project has it's own AssemblyInfo.cs - this contains AssemblyTitle, AssemblyDescription, Guid, and other attributes that are unique to that assembly.
Each project also has two other Assembly Info files, note that these are added as a link rather than a direct file (VS -> Add -> Existing File -> Add as link (little down arrow next to add))
The two link files:
CompanyAssemblyInfo.cs - AssemblyCompany, AssemblyCopyright, AssemblyConfiguration, CLSCompliant, SecurityPermission, etc. Basically everything we want standard on all our assemblies.
ProductAssemblyInfo.cs - AssemblyProduct, AssemblyVersion, AssemblyFileVersion. This allows us to push the same version across to all assemblies from the one file.
Our CI and release process is more complicated, but that's at the heart of it - a single point (file) which controls the product version (assemblies, installers, everything!)
There's a task to do just what you're asking about.
You'll need to install the MSBuildCommunity tasks, found here.
Then, you can create something like this:
<PropertyGroup>
<MyAssemblyVersion>$(CCNetLabel)</MyAssemblyVersion>
</PropertyGroup>
<Target Name="GenAssemblyInfo">
<AssemblyInfo
ContinueOnError="false"
CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\YourAssembly\AssemblyInfo.cs"
AssemblyTitle="blah"
AssemblyDescription="blah blah"
AssemblyCompany="Anj Software, Inc."
AssemblyProduct="Anj's Awesome App"
AssemblyCopyright="blah blah"
CLSCompliant="false"
AssemblyVersion="$(MyAssemblyVersion)"
AssemblyFileVersion="$(MyAssemblyVersion)"
/>
</Target>
Note that you can set a build number prefix in your ccnet.config file so that your assemblies will be numbered 2.1.0.x where x is the build number. That's how we do our version numbering where I work.
You'll still need to keep a default AssemblyInfo.cs file as part of each of the projects that make up your solution.
I use powershell for this. lpath is the path to the source code, and buildnum is my buildnumber I append. That is all I actually do with this. However, it should give you enough to change or set any or all of the other fields available. I pass in lpath and I get the buildnumber from the available environment variables in CC.NET and I can use this script over and over again, just changing what I pass in on the command line in the config file. I also have one that modifies the resource files for the C++ Code if that is actually what you need to modify.
$files = Get-ChildItem $lpath -recurse -filter *AssemblyInfo.cs -name
Foreach ($file in $files)
{
$file = $lpath + "\" + $file
$fileObject=get-item $file
$fileObject.Set_IsReadOnly($False)
$sr = new-object System.IO.StreamReader( $file, [System.Text.Encoding]::GetEncoding("utf-8") )
$content = $sr.ReadToEnd()
$sr.Close()
$content = [Regex]::Replace( $content, '(?<=\[assembly: AssemblyVersion\("[0-9].[0-9].[0-9].)[0-9]?[0-9]?[0-9]', $buildnum);
$content = [Regex]::Replace( $content, '(?<=\[assembly: AssemblyFileVersion\("[0-9].[0-9].[0-9].)[0-9]?[0-9]?[0-9]', $buildnum);
$sw = new-object System.IO.StreamWriter( $file, $false, [System.Text.Encoding]::GetEncoding("utf-8") )
$sw.Write( $content )
$sw.Close()
$fileObject.Set_IsReadOnly($True)
}

Resources