Wednesday, November 23, 2022

Slow VMWare Player after upgrade to Windows 11

Recently my Windows 10 PC was upgraded to Windows 11. One problem which I noticed after that was slowness of VMWare Player on Windows 11 comparing with Windows 10. There are several posts about similar problem which may be found in internet - almost all of them suggest few things: turn off Hyper-V Windows feature, run command "bcdedit /set hypervisorlaunchtype off", disable memory integrity in Windows Security > Device Security > Core Isolation details. I tried them but it didn't help.

I also tried to update to the latest VMWare Player 17 which according to it's description fully supports Windows 11 as host operating system. That didn't help either.

Also some posts suggested obvious things like increasing memory and CPU. I tried to increase memory a bit (virtual machine already had 16Gb RAM and 8 processor cores which should be enough. Remember that on Windows 10 it worked very fast) without success.

Then I tried opposite thing: reduce number of processor cores from 8 to 4:

and surprisingly it helped! VMWare Player became fast again. This solution may look non logical but may help someone so I decided to share it.

Wednesday, November 16, 2022

Camlex and Camlex.Client 5.4.1 released

I'm glad to announce that today new version 5.4.1 of Camlex/Camlex.Client libraries were released. This is minor release which contains fix for reverse engineering of binary operations (Geq, Gt, Leq, Lt) for text values. Reverse engineering is used in free online service http://camlex-online.org where Sharepoint developers which are new to Camlex can automatically convert classic CAML query to C# syntax for Camlex. Thus it will simplify migration of existing code to Camlex.

Credits for this release go to Ivan Russo which contributed to Camlex (thanks a lot Ivan). If you are Sharepoint developer and have idea how to improve Camlex feel free to create PR :).

Friday, September 23, 2022

Generate C# client for API with Swagger (OpenAPI) support

If you develop web API it worth to consider to add support for Swagger specification (or OpenAPI specification) to it. As documentation says:

OAS defines an API’s contract, allowing all the API’s stakeholders, be it your development team, or your end consumers, to understand what the API does and interact with its various resources, without having to integrate it into their own application.

One of the practical advantage is possibility to easily create documentation for web API which supports Swagger. Another big advantage is possibility to generate SDK (client) for web API using preferred language. There are Swagger Codegen tools for that. One of them is online web service https://generator3.swagger.io/api/generate which can be used for generating SDK online.

Let's see how to use that and generate C# client for sample web API https://petstore.swagger.io/v2/swagger.json with Swagger support. We need to send HTTP POST request tohttps://generator3.swagger.io/api/generate with the following body:

{
  "specURL" : "https://petstore.swagger.io/v2/swagger.json",
  "lang" : "csharp",
  "type" : "CLIENT",
  "codegenVersion" : "V3"
}

and add header "Content-Type": "application/json". In "lang" parameter we specified which language should be used for generating SDK. After that web service will generate C# client for us and will return generated .cs classes in zip archive. If you use Postman then instead of Send button need to use Send and Download:


Zip archive will contain Visual Studio solution with generated SDK for web API:

As you can see it contains generated model classes and API for working with them so no need to code it manually. It may save a lot of time when you work with external web API.

Wednesday, September 7, 2022

Fix problem with PnP.PowerShell log file locked for writing by Set-PnPTraceLog

If you use PnP.PowerShell then most probably you are familiar with Set-PnPTraceLog cmdlet which allows to enable logging from PnP cmdlets. This is useful feature which simplifies troubleshooting especially if error got reproduced only on customer's environment. However there is one unpleasant side effect: Set-PnPTraceLog locks log file for writing. I.e. it is not possible to reuse the same file for other purposes and write there from anoter sources. Let's see why it happens.

Internally Set-PnPTraceLog uses TextWriterTraceListener (thanks Gautam Sheth for sharing it):

If we will decompile code of TextWriterTraceListener (which is quite easy in VS2022) we will find that it opens FileStream with FileShare.Read option:


And that's exactly the reason why it is not possible to write anything else to this log file until PowerShell session where Set-PnPTraceLog was called will be closed.

In order to solve this problem we need to use our own FileStream and inject it to PnP logging system. It can be done by the following PowerShell code:

# enable logging
Set-PnPTraceLog -On -LogFile $logFilePath -Level Debug
# close default file stream
[System.Diagnostics.Trace]::Listeners[1].Writer.Close()
# open new file stream with FileShare.ReadWrite
$fileStream = New-Object System.IO.FileStream($logFilePath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite, 4096)
# inject new file stream to PnP
[System.Diagnostics.Trace]::Listeners[1].Writer = New-Object System.IO.StreamWriter($fileStream, [System.Text.Encoding]::UTF8, 4096, $false)

Here we first enable PnP logging Set-PnPTraceLog. Every process by default already has trace listener called DefaultTraceListener which is accessible from Trace.Listeners[0]. In order to access listener added by Set-PnPTraceLog we need access Trace.Listeners[1]. So after that we open new FileStream with FileShare.ReadWrite and then use it for StreamWriter which we inject to PnP. As result PnP will use our own FileStream and it will be possible to write to the same log from another sources.

Friday, August 19, 2022

Fix for "The system cannot open the device or file specified" error in Windows Installer

Some time ago I wrote instructions how to increase space on C drive by moving Windows Installer folder C:\Windows\SoftwareDistribution to D drive and creating junction link (see How to free space on OS Windows drive by moving SoftwareDistribution folder to another disk via junction folder). It works but there may be side effects: one of known issue is that after that some (not all) installations from msi files won't work. E.g. I faced with the following error when tried to installed NodeJS:

The system cannot open the device or file specified

 


One solution which you may try is the following:

  1. Remove junction link for C:\Windows\SoftwareDistribution folder (but keep actual folder which contains files on D drive!) and create normal folder C:\Windows\SoftwareDistribution (it will be empty)
  2. Run installer - it should work without errors and should put sub folders of new installation into C:\Windows\SoftwareDistribution
  3. After installation move sub folders from C:\Windows\SoftwareDistribution to SoftwareDistribution on D drive
  4. Delete C:\Windows\SoftwareDistribution folder
  5. Create junction link again

However there is simpler way to avoid mentioned error: run installer in silent mode from PowerShell. I found useful script which does it here. Will post it here in case link won't be available:

$fileName = "..."

$DataStamp = get-date -Format yyyyMMddTHHmmss
$logFile = '{0}-{1}.log' -f $fileName,$DataStamp
$MSIArguments = @(
    "/i"
    ('"{0}"' -f $fileName)
    "/qn"
    "/norestart"
    "/L*v"
    $logFile
)
Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow 

Before to run it put full path to your msi file into $fileName variable. Script will run installer in silent mode and will write all steps into log file so you may check results. After that program should appear in the list of installed apps in Control panel > Programs and features.

Wednesday, August 17, 2022

Get Sharepoint data in browser console via Rest API without additional tools

Sometimes during troubleshooting you need to quickly get some data from Sharepoint, e.g. id of current site collection. There are many ways how to do that with additional tools e.g. from PowerShell and PnP, SPEditor Chrome extension and it's pnpjs console, etc. But it requires installation of these tools and their knowledge (of course if you work with Sharepoint it will be better if you will know these tools :) ).

One way how you may get this data without extra tools is to use SP Rest API directly from browser console. E.g. for getting site collection details we may fetch /_api/site endpoint and output JSON response to console:

fetch("https://{mytenant}.sharepoint.com/sites/test/_api/site", {headers: {"accept": "application/json; odata=verbose"}}).then(response => response.json().then(txt => console.log(JSON.stringify(txt))))

(here instead of {mytenant} you should use your tenant name. Note however that this approach will also work in on-prem)

It will output a lot of information about current site collection to the console:

{
    "d": {
        "__metadata": {
            "id": "https://{mytenant}.sharepoint.com/sites/test/_api/site",
            "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site",
            "type": "SP.Site"
        },
        "Audit": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Audit"
            }
        },
        "CustomScriptSafeDomains": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/CustomScriptSafeDomains"
            }
        },
        "EventReceivers": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/EventReceivers"
            }
        },
        "Features": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Features"
            }
        },
        "HubSiteSynchronizableVisitorGroup": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/HubSiteSynchronizableVisitorGroup"
            }
        },
        "Owner": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Owner"
            }
        },
        "RecycleBin": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/RecycleBin"
            }
        },
        "RootWeb": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/RootWeb"
            }
        },
        "SecondaryContact": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/SecondaryContact"
            }
        },
        "UserCustomActions": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/UserCustomActions"
            }
        },
        "AllowCreateDeclarativeWorkflow": false,
        "AllowDesigner": true,
        "AllowMasterPageEditing": false,
        "AllowRevertFromTemplate": false,
        "AllowSaveDeclarativeWorkflowAsTemplate": false,
        "AllowSavePublishDeclarativeWorkflow": false,
        "AllowSelfServiceUpgrade": true,
        "AllowSelfServiceUpgradeEvaluation": true,
        "AuditLogTrimmingRetention": 90,
        "ChannelGroupId": "00000000-0000-0000-0000-000000000000",
        "Classification": "",
        "CompatibilityLevel": 15,
        "CurrentChangeToken": {
            "__metadata": {
                "type": "SP.ChangeToken"
            },
            "StringValue": "..."
        },
        "DisableAppViews": false,
        "DisableCompanyWideSharingLinks": false,
        "DisableFlows": false,
        "ExternalSharingTipsEnabled": false,
        "GeoLocation": "EUR",
        "GroupId": "00000000-0000-0000-0000-000000000000",
        "HubSiteId": "00000000-0000-0000-0000-000000000000",
        "Id": "32d406dc-dc97-46dd-b01c-e6346419ceb7",
        "SensitivityLabelId": null,
        "SensitivityLabel": "00000000-0000-0000-0000-000000000000",
        "IsHubSite": false,
        "LockIssue": null,
        "MaxItemsPerThrottledOperation": 5000,
        "MediaTranscriptionDisabled": false,
        "NeedsB2BUpgrade": false,
        "ResourcePath": {
            "__metadata": {
                "type": "SP.ResourcePath"
            },
            "DecodedUrl": "https://{mytenant}.sharepoint.com/sites/test"
        },
        "PrimaryUri": "https://{mytenant}.sharepoint.com/sites/test",
        "ReadOnly": false,
        "RequiredDesignerVersion": "15.0.0.0",
        "SandboxedCodeActivationCapability": 2,
        "ServerRelativeUrl": "/sites/test",
        "ShareByEmailEnabled": false,
        "ShareByLinkEnabled": false,
        "ShowUrlStructure": false,
        "TrimAuditLog": true,
        "UIVersionConfigurationEnabled": false,
        "UpgradeReminderDate": "1899-12-30T00:00:00",
        "UpgradeScheduled": false,
        "UpgradeScheduledDate": "1753-01-01T00:00:00",
        "Upgrading": false,
        "Url": "https://{mytenant}.sharepoint.com/sites/test",
        "WriteLocked": false
    }
}

sUsing the same approach you may call another Rest API end points directly from browser console. It may save your time during troubleshooting. Hope this information will help someone.

Friday, August 12, 2022

Move C:/Users/{username}/AppData folder to D drive

This article may be useful for those PC owners who has small SSD C drive and needs additional space there without uninstalling apps. Some time ago I wrote an article how to free space on C drive using junction folder links: How to free space on OS Windows drive by moving SoftwareDistribution folder to another disk via junction folder.

Here we will use the same idea but for C:/Users/{username}/AppData folder. In my case WinDirStat showed that most of space is occupied in this folder for my basic user. Moving files from there to D drive included several steps:

1. Create new admin user account and login with this account

2. Go to C:/Users/{username}/AppData for your basic user and rename folder to C:/Users/{username}/AppData.old. If Windows will complain that folder is in use try to use Resource Monitor > CPU > Associated handles tab and search by folder name. It will show all processes which currently use this folder. After that you may kill these processes in Task Manager > Details tab: there is User name column so you may choose those apps which work for your basic user account (and which use files from C:/Users/{username}/AppData).

3. Create new folder on D drive, e.g. D:/UserNameAppData

4. Create junction link using the following command in cmd:

mklink /j C:\Users\{username}\AppData D:\UserNameAppData

5. Move all files from C:/Users/{username}/AppData.old to D:\UserNameAppData

6. Restart PC and login with your basic account

In my case after that Windows start icon stopped working. I tried many instructions to restore it but only the following actually helped: The tale of how I managed to solve a nasty start menu corruption. Will duplicate it here in case this link won't be available:

taskkill /F /IM explorer.exe
taskkill /F /IM SearchApp.exe
taskkill /F /IM SearchUI.exe
taskkill /F /IM ShellExperienceHost.exe
taskkill /F /IM StartMenuExperiencehost.exe
Start-Sleep 2

Set-Location $env:LOCALAPPDATA\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy
Remove-Item -Recurse -Force .\TempState\

Set-Location $env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperiencehost_cw5n1h2txyewy
Remove-Item -Recurse -Force .\TempState\

Add-AppxPackage -Register "C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode
Add-AppxPackage -Register "C:\Windows\SystemApps\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode

Start-Process explorer.exe

After that Windows start icon started to work but search in start menu still didn't work. For restoring search in Windows start menu I used another PowerShell command which I found here:

Get-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml"}

After that search also started to work. Note that if your D drive is not SSD apps may work slower after this change because files will be physically stored on D drive now. Hope that this information will help someone.