<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Niveau expert &#8211; Kodono</title>
	<atom:link href="https://blog.kodono.info/wordpress/category/niveau-expert/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.kodono.info/wordpress</link>
	<description>Pour tous les technophiles</description>
	<lastBuildDate>Sat, 08 Feb 2025 17:42:21 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.1</generator>
	<item>
		<title>Utiliser un VPN sur l&#8217;iPad via un serveur Debian</title>
		<link>https://blog.kodono.info/wordpress/2025/02/08/utiliser-un-vpn-sur-lipad-via-un-serveur-debian/</link>
					<comments>https://blog.kodono.info/wordpress/2025/02/08/utiliser-un-vpn-sur-lipad-via-un-serveur-debian/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sat, 08 Feb 2025 17:42:21 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[Français]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2352</guid>

					<description><![CDATA[Je vais expliquer ici comment installer un serveur VPN WireGuard sur Debian et l&#8217;utiliser depuis un iPad. 1. Serveur Debian Sur le serveur, on installe WireGuard avec apt install wireguard -y. 2. Générer la clé privée et publique du serveur Pour cela, on va faire : sudo wg genkey &#124; sudo tee /etc/wireguard/server_private.key sudo cat [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Je vais expliquer ici comment installer un serveur VPN WireGuard sur Debian et l&#8217;utiliser depuis un iPad.</p>
<p><strong>1. Serveur Debian</strong></p>
<p>Sur le serveur, on installe WireGuard avec <code>apt install wireguard -y</code>.</p>
<p><strong>2. Générer la clé privée et publique du serveur</strong></p>
<p>Pour cela, on va faire : </p>
<pre class="brush:bash">
sudo wg genkey | sudo tee /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_private.key | sudo wg pubkey | sudo tee /etc/wireguard/server_public.key
</pre>
<p>On retrouvera nos clés dans les fichiers <code>/etc/wireguard/server_private.key</code> et <code>/etc/wireguard/server_public.key</code></p>
<p><strong>3. Générer la clé privée et publique du client</strong></p>
<p>Sur le serveur Debian, on va générer des clés pour le client :</p>
<pre class="brush:bash">
wg genkey | tee client_private.key
cat client_private.key | wg pubkey | tee client_public.key
</pre>
<p>On retrouvera nos clés dans les fichiers <code>./client_private.key</code> et <code>./client_public.key</code></p>
<p><strong>4. Configuration du serveur</strong></p>
<p>On va entrer la configuration suivante dans le fichier <code>/etc/wireguard/wg0.conf</code> :</p>
<pre>
[Interface]
Address = 10.0.0.1/24
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = PRIVATE_KEY_DU_SERVEUR

[Peer]
PublicKey = PUBLIC_KEY_DU_CLIENT
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25
</pre>
<p><strong>5. Activer le transfert IP sur le serveur</strong></p>
<p>Éditer le fichier <code>/etc/sysctl.conf</code> afin d&#8217;avoir :</p>
<pre>
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
</pre>
<p>Et on applique les changements avec la commande <code>sudo sysctl -p</code></p>
<p><strong>6. Démarrage de WireGuard</strong></p>
<p>Pour démarrer le serveur VPN on tape <code>sudo systemctl start wg-quick@wg0</code></p>
<p>On peut voir le statut avec la commande <code>sudo systemctl status wg-quick@wg0</code></p>
<p><strong>7. Configuration du client</strong></p>
<p>Sur notre client (ici un iPad), on installe l&#8217;application WireGuard depuis le App Store.</p>
<p>Ensuite, sur notre serveur, on va créer le fichier de configuration <code>client.conf</code> qui sera utilisé par le client, avec le contenu suivant :</p>
<pre>
[Interface]
PrivateKey = PRIVATE_KEY_DU_CLIENT
Address = 10.0.0.2/32
DNS = 8.8.8.8, 8.8.4.4 # on utilise les DNS de Google

[Peer]
PublicKey = PUBLIC_KEY_DU_SERVEUR
Endpoint = mon_serveur.debian.home:51820 # on indique l'IP/hostname de notre serveur VPN
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
</pre>
<p>Afin de transmettre cette configuration à l&#8217;iPad, on peut générer un QR code. Pour cela on installe ce qu&#8217;il faut : <code>sudo apt install qrencode</code>, puis on génère avec <code>qrencode -t ansiutf8 < client.conf</code></p>
<p>Sur l'iPad, on ouvre l'application WireGuard puis on ajoute un client en utilisant le QR Code généré.</p>
<p><strong>8. Vérification</strong></p>
<p>En activant le VPN sur l'iPad, on peut tester si tout fonctionne comme prévu en vérifiant l'adresse IP de l'iPad.<br />
Sur le serveur, on peut utiliser la commande <code>wg show</code> pour voir un peu ce qu'il se passe.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2025/02/08/utiliser-un-vpn-sur-lipad-via-un-serveur-debian/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to run Android TV 9 on Windows</title>
		<link>https://blog.kodono.info/wordpress/2024/09/02/how-to-run-android-tv-9-on-windows/</link>
					<comments>https://blog.kodono.info/wordpress/2024/09/02/how-to-run-android-tv-9-on-windows/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 02 Sep 2024 09:34:00 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2311</guid>

					<description><![CDATA[I used to use Android Studio with the various emulators to test my Android apps, however for Android TV 9 it doesn&#8217;t work properly because it doesn&#8217;t have Google Play Store on it. To have a good version of Android TV 9: Downlad a VirtualBox image of it – I used the one shared in [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I used to use Android Studio with the various emulators to test my Android apps, however for Android TV 9 it doesn&#8217;t work properly because it doesn&#8217;t have Google Play Store on it.</p>
<p>To have a good version of Android TV 9:</p>
<ol>
<li><a href="https://mega.nz/file/XQ9zWaoC#PajlBLEU-43g239wAB0vpMsnpYpEU6seSqz36lXv5go">Downlad a VirtualBox image of it</a> – I used the one shared <a href="https://www.youtube.com/watch?v=_dfOESBBTHM">in this video</a>
<li>Install <a href="https://www.virtualbox.org/">VirtualBox</a> if you don&#8217;t have it yet</li>
<li>Load the downloaded image with VirtualBox</li>
<li>Configure it by going to the network section and select &#8220;Bridged Adapter&#8221; instead of &#8220;NAT&#8221;</li>
<li>Once Android TV is started, you&#8217;ll have to connect to your Google account, but using the keyboard might be challenging – you can right click on the USB cable icon that appears at the bottom right of the VirtualBox window, and select your keyboard (be aware it&#8217;s a QWERTY map that is used)</li>
</ol>
<p>After that, go to the Android TV settings, in the &#8220;About&#8221; section, and click several times on the Build Version to enable the Developer Mode. From the developer menu, you can enable the &#8220;Debug USB&#8221;.</p>
<p>Once everything is correctly configured, the Android TV should be visible on your local network, meaning you can use the ADB command to test your app.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2024/09/02/how-to-run-android-tv-9-on-windows/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Connect to SharePoint Online from NodeJS using an impersonate (application) Bearer Token</title>
		<link>https://blog.kodono.info/wordpress/2024/04/29/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bearer-token/</link>
					<comments>https://blog.kodono.info/wordpress/2024/04/29/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bearer-token/#comments</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 29 Apr 2024 13:07:56 +0000</pubDate>
				<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2291</guid>

					<description><![CDATA[(duplicate of this StackOverflow answer) Start a new project In a new folder, type npm init to start a new project. Make sure to use a Node &#62;= v14 (I use Node v18 – and Volta can be useful to manage several versions of Node for Windows) Install some dependencies: npm install axios @azure/msal-node uuid [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>(duplicate of this <a href="https://stackoverflow.com/questions/78402212/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bear/78402213#78402213">StackOverflow answer</a>)</p>
<h1 id="start-a-new-project">Start a new project</h1>
<ol>
<li>In a new folder, type <code>npm init</code> to start a new project.</li>
<li>Make sure to use a Node &gt;= v14 (I use Node v18 – and <a href="https://volta.sh/">Volta</a> can be useful to manage several versions of Node for Windows) </li>
<li>Install some dependencies: <code>npm install axios @azure/msal-node uuid</code>  </li>
</ol>
<h1 id="certificate">Certificate</h1>
<p>In the past, we could use the add-in application feature to get a token, but Microsoft <a href="https://learn.microsoft.com/fr-fr/sharepoint/dev/sp-add-ins/retirement-announcement-for-add-ins">announced</a> it will be retired.</p>
<p>We now need to pass through an Azure application to get it. But before creating the Azure app, we need to create a Private Certificate key.</p>
<ol>
<li>Create the file <code>Create-SelfSignedCertificate.ps1</code> using the below code (<a href="https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#setting-up-an-azure-ad-app-for-app-only-access">source</a>):   </li>
</ol>
<pre class="brush:powershell">#Requires -RunAsAdministrator
<#
.SYNOPSIS
Creates a Self Signed Certificate for use in server to server authentication
.DESCRIPTION
Source: https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#setting-up-an-azure-ad-app-for-app-only-access
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21
This will create a new self signed certificate with the common name "CN=MyCert". During creation you will be asked to provide a password to protect the private key.
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21 -Password (ConvertTo-SecureString -String "MyPassword" -AsPlainText -Force)
This will create a new self signed certificate with the common name "CN=MyCert". The password as specified in the Password parameter will be used to protect the private key
.EXAMPLE
.\Create-SelfSignedCertificate.ps1 -CommonName "MyCert" -StartDate 2015-11-21 -EndDate 2017-11-21 -Force
This will create a new self signed certificate with the common name "CN=MyCert". During creation you will be asked to provide a password to protect the private key. If there is already a certificate with the common name you specified, it will be removed first.
#>
Param(

[Parameter(Mandatory=$true)]
   [string]$CommonName,

[Parameter(Mandatory=$true)]
   [DateTime]$StartDate,

[Parameter(Mandatory=$true)]
   [DateTime]$EndDate,

[Parameter(Mandatory=$false, HelpMessage="Will overwrite existing certificates")]
   [Switch]$Force,

[Parameter(Mandatory=$false)]
   [SecureString]$Password
)

# DO NOT MODIFY BELOW
function CreateSelfSignedCertificate(){
  #Remove and existing certificates with the same common name from personal and root stores
    #Need to be very wary of this as could break something
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object{$_.Subject -eq "CN=$CommonName"}
    if($certs -ne $null -and $certs.Length -gt 0)
    {
        if($Force)
        {

foreach($c in $certs)
            {
                remove-item $c.PSPath
            }
        } else {
            Write-Host -ForegroundColor Red "One or more certificates with the same common name (CN=$CommonName) are already located in the local certificate store. Use -Force to remove them";
            return $false
        }
    }

$name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
    $name.Encode("CN=$CommonName", 0)

$key = new-object -com "X509Enrollment.CX509PrivateKey.1"
    $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
    $key.KeySpec = 1
    $key.Length = 2048
    $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
    $key.MachineContext = 1
    $key.ExportPolicy = 1 # This is required to allow the private key to be exported
    $key.Create()

$serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
    $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1") # Server Authentication
    $ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
    $ekuoids.add($serverauthoid)
    $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
    $ekuext.InitializeEncode($ekuoids)

$cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
    $cert.InitializeFromPrivateKey(2, $key, "")
    $cert.Subject = $name
    $cert.Issuer = $cert.Subject
    $cert.NotBefore = $StartDate
    $cert.NotAfter = $EndDate
    $cert.X509Extensions.Add($ekuext)
    $cert.Encode()

$enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
    $enrollment.InitializeFromRequest($cert)
    $certdata = $enrollment.CreateRequest(0)
    $enrollment.InstallResponse(2, $certdata, 0, "")
    return $true
}

function ExportPFXFile()
{
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    if($Password -eq $null)
    {
        $Password = Read-Host -Prompt "Enter Password to protect private key" -AsSecureString
    }
    $cert = Get-ChildItem -Path Cert:\LocalMachine\my | where-object{$_.Subject -eq "CN=$CommonName"}

    Export-PfxCertificate -Cert $cert -Password $Password -FilePath "$($CommonName).pfx"
    Export-Certificate -Cert $cert -Type CERT -FilePath "$CommonName.cer"
}

function RemoveCertsFromStore()
{
    # Once the certificates have been been exported we can safely remove them from the store
    if($CommonName.ToLower().StartsWith("cn="))
    {
        # Remove CN from common name
        $CommonName = $CommonName.Substring(3)
    }
    $certs = Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object{$_.Subject -eq "CN=$CommonName"}
    foreach($c in $certs)
    {
        remove-item $c.PSPath
    }
}

if(CreateSelfSignedCertificate)
{
    ExportPFXFile
    RemoveCertsFromStore
}</pre>
<ol start="2">
<li>Open a PowerShell console as an <em>administrator</em></li>
<li>Create the certificate with a command like <code>.\Create-SelfSignedCertificate.ps1 -CommonName &quot;SharePointOnlinePrivateKey&quot; -StartDate 2024-04-01 -EndDate 2035-01-01</code> (if you receive an error, make sure to allow running this kind of script with the command <code>Set-ExecutionPolicy RemoteSigned</code>)</li>
<li>A password is required (e.g. &quot;HereIsMyPass1223&quot;)</li>
<li>Two files are created: <code>SharePointOnlinePrivateKey.pfx</code> and <code>SharePointOnlinePrivateKey.cer</code></li>
</ol>
<p>We&#39;re going to install OpenSSL to convert the <code>pfx</code> file to a <code>pem</code>:</p>
<ol>
<li>Install <a href="https://www.openssl.org/">OpenSSL</a> (e.g. the light version for Win64) as an <em>administrator</em></li>
<li>Find the OpenSSL installation directory (e.g. <code>C:\Program Files\OpenSSL-Win64\</code>)</li>
<li>Open the <code>start.bat</code> file from this OpenSSL directory</li>
<li>A command window opens – go to the directory where the <code>SharePointOnlinePrivateKey.pfx</code> file is (e.g. <code>cd C:\Users\Aymeric\Documents\nodejs\spo-experiments</code>)</li>
<li>In the OpenSSL command window, type <code>openssl pkcs12 -in SharePointOnlinePrivateKey.pfx -out SharePointOnlinePrivateKey.pem</code> (the password entered in the previous steps will be asked three times)</li>
</ol>
<p>We should now have a file called <code>SharePointOnlinePrivateKey.pem</code></p>
<h1 id="azure-application">Azure Application</h1>
<p>It&#39;s time to create the related Azure application:</p>
<ol>
<li>Go to <a href="https://portal.azure.com">https://portal.azure.com</a></li>
<li>Go to the Microsoft Entra ID section</li>
<li>Go to the &quot;App Registrations&quot;, and click on &quot;New registration&quot;</li>
<li>I won&#39;t detail all the steps to create the app</li>
<li>Give a name to the app (e.g. &quot;<strong>SharePoint Online Remote Access</strong>&quot;)</li>
</ol>
<h2 id="get-thumbprint">Get Thumbprint</h2>
<p>We need the thumbprint:</p>
<ol>
<li>Go to &quot;<strong>Certificates &amp; secrets</strong>&quot; section</li>
<li>Go to &quot;<strong>Certificates</strong>&quot; tab (<a href="https://github.com/SharePoint/sp-dev-docs/issues/5889#issuecomment-645225813">see how</a>) and upload the <code>SharePointOnlinePrivateKey.cer</code> file you created before</li>
<li>Once uploaded, it will provide &quot;<strong>Thumbprint</strong>&quot; (e.g. &quot;F7D8D4F2F140E79B215899BD93A14D0790947789&quot;) – copy this value for a later use.</li>
</ol>
<h2 id="get-client-id-and-tenant-id">Get Client Id and Tenant Id</h2>
<p>We need the <code>clientId</code> and the <code>tenantId</code>:</p>
<ol>
<li>From the overview page of your app, copy the &quot;<strong>Application (client) Id</strong>&quot; (e.g. &quot;75284292-7515-4e2f-aae9-d1777527dd7b&quot;) and the &quot;<strong>Directory (tenant) ID</strong>&quot; (e.g. &quot;945c177a-83a2-4e80-9f8c-5a91be5752dd&quot;)</li>
</ol>
<h2 id="platform-configuration">Platform configuration</h2>
<p>Additional configuration required for the application:</p>
<ol>
<li>Go to &quot;<strong>Authentication</strong>&quot; menu, and under &quot;<strong>Platform configurations</strong>&quot;, click on &quot;<strong>Add a Platform</strong>&quot;</li>
<li>Choose &quot;<strong>Web</strong>&quot; and enter <code>https://localhost</code> for &quot;<strong>Redirect URLs</strong>&quot;</li>
<li>Choose &quot;<strong>Access Token</strong>&quot; and &quot;<strong>ID Token</strong>&quot; in the &quot;<strong>Implicit grant and hybrid flows</strong>&quot; section</li>
</ol>
<h2 id="api-permissions">API Permissions</h2>
<p>We want to give the permissions to the Azure app to get access to only one (or more) specific Sites Collection, but not all of the tenant site collections. To do so, we only need the <code>Sites.Selected</code> permissions (<a href="https://devblogs.microsoft.com/microsoft365dev/controlling-app-access-on-specific-sharepoint-site-collections/">ref1</a> and <a href="https://techcommunity.microsoft.com/t5/microsoft-sharepoint-blog/develop-applications-that-use-sites-selected-permissions-for-spo/ba-p/3790476">ref2</a>).</p>
<p>First, let&#39;s select it from a Microsoft Graph perspective:</p>
<ol>
<li>Go to the &quot;<strong>API Permissions</strong>&quot; section from the left navigation</li>
<li>Click on &quot;<strong>Add a permission</strong>&quot;</li>
<li>Select &quot;<strong>Microsoft Graph</strong>&quot;</li>
<li>Then &quot;<strong>Application Permissions</strong>&quot;</li>
<li>Select &quot;<strong>Sites.Selected</strong>&quot;</li>
</ol>
<p>Then, let&#39;s select it for SharePoint Online REST perspective:</p>
<ol>
<li>From the same section, select &quot;<strong>SharePoint</strong>&quot;</li>
<li>Then &quot;<strong>Application Permissions</strong>&quot;</li>
<li>Select &quot;<strong>Sites.Selected</strong>&quot;</li>
</ol>
<p><a href="https://i.sstatic.net/19SV6jA3.png"><img decoding="async" src="https://i.sstatic.net/19SV6jA3.png" alt="API Permissions screen"></a><br />
Remark: you might need to contact the IT team to get your API Permissions approved/granted.</p>
<h2 id="get-client-secret">Get Client Secret</h2>
<p>We need the <code>clientSecret</code>:</p>
<ol>
<li>Go to the &quot;<strong>Certificates and Secrets</strong>&quot; section from the left navigation</li>
<li>Go to &quot;<strong>Client Secret</strong>&quot; and click on &quot;<strong>New client secret</strong>&quot;</li>
<li>In &quot;<strong>Description</strong>&quot; you can put something like &quot;SharePoint Online Remote Access&quot;, and choose 365 days for the expiration</li>
<li>Once done, make sure to copy <code>Value</code> (e.g. &quot;rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt&quot;) that is our <code>clientSecret</code></li>
</ol>
<h2 id="app-permissions-on-site-collection">App permissions on Site Collection</h2>
<p>It&#39;s time to indicate that the Azure application can have access to a specific Site Collection (e.g. <a href="https://mycompany.sharepoint.com/sites/contoso">https://mycompany.sharepoint.com/sites/contoso</a>).</p>
<p>To proceed, we need the <code>siteId</code>:</p>
<ol>
<li>Go to the SharePoint website (e.g. <a href="https://mycompany.sharepoint.com/sites/contoso">https://mycompany.sharepoint.com/sites/contoso</a>)</li>
<li>Click right in the page to see the source (or <code>CTRL U</code>)</li>
<li>Search for the variable <code>siteId</code> (e.g. &quot;7d6d9e18-e8de-4b7c-9582-3f978726f356&quot;)</li>
</ol>
<p>To give the permissions to the app, a special command must be entered (see <a href="https://www.youtube.com/watch?v=SNIF3zCYNUk">this video</a> for more information). In theory, the site collection admin can do it, but it might be restricted in your organization, and you&#39;ll need the assistance of a tenant admin.</p>
<p>We need the <code>AppId</code> (as known as <code>clientId</code>) and <code>DisplayName</code> (as known as the Azure app name):</p>
<pre class="brush:powershell">
$targetSiteUri = 'https://mycompany.sharepoint.com/sites/contoso'
Connect-PnpOnline $targetSiteUri
Grant-PnPAzureADAppSitePermission -AppId '75284292-7515-4e2f-aae9-d1777527dd7b' -DisplayName 'SharePoint Online Remote Access' -Site $targetSiteUri -Permissions Write
</pre>
<h1 id="interact-with-sharepoint">Interact with SharePoint</h1>
<p>Microsoft Graph only needs a <code>client_secret</code>, while SharePoint REST API needs a <code>client_assertion</code> from the Certificate Private key.</p>
<p>So, let&#39;s start with Microsoft Graph to verify our Azure app has the correct access permissions.</p>
<h2 id="microsoft-graph">Microsoft Graph</h2>
<p>Below is the script we can use:</p>
<pre class="brush:javascript">
const axios = require('axios');

// our constants that we found previously
// they should not be hard-coded in your script, but store in a safe place
const tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';
const clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';
const clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';
const siteId = '7d6d9e18-e8de-4b7c-9582-3f978726f356';

async function getAccessToken() {
  const resource = 'https://graph.microsoft.com';

  try {
    const response = await axios.post(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: clientId,
      client_secret: clientSecret,
      scope: `${resource}/.default`
    }), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data.access_token;
  } catch (error) {
    console.error('Error getting access token:', error);
    throw error;
  }
}

// get a SharePoint item using Graph
getAccessToken()
.then(token => {
  // we need to find the siteId for each level
  // we could use `https://graph.microsoft.com/v1.0/sites/${siteId}/sites` to find the children sites Id
  // mycompany.sharepoint.com/sites/contoso/Toolbox -> 919f3ff8-2cfd-469d-ac2c-cf58475ee72a
  // mycompany.sharepoint.com/sites/contoso/Toolbox/Demo -> 37af7205-ebd1-49e5-a770-cdb182d2ae81
  return axios.get(`https://graph.microsoft.com/v1.0/sites/${siteId}/sites/919f3ff8-2cfd-469d-ac2c-cf58475ee72a/sites/37af7205-ebd1-49e5-a770-cdb182d2ae81/lists/Assets/items/123?expand=fields(select=Title)`, {
    headers:{
      'Authorization':'Bearer '+token,
      'Accept': 'application/json'
    }
  })
})
.then(res => {
  console.log(res.data);
})
.catch(err => {
  console.log("err => ", err.response.data)
})
</pre>
<h2 id="sharepoint-online-rest-api">SharePoint Online REST API</h2>
<p>If it worked with the Graph version, it means we can now test with the SharePoint REST API:</p>
<pre class="brush:javascript">
const axios = require('axios');
const fs = require("fs");
const crypto = require("crypto");
const msal = require("@azure/msal-node");
const { v4: uuid } = require('uuid');

// our constants that we found previously
// they should not be hard-coded in your script, but store in a safe place
const tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';
const clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';
const clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';
const resource = 'https://mycompany.sharepoint.com';
const privateKeyPassPhrase = "HereIsMyPass1223";
const thumbprint = "F7D8D4F2F140E79B215899BD93A14D0790947789";

// generate the client_assertion
function getClientAssertion () {
  // source: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/certificate-credentials.md
  // decrypt the private key
  const privateKeySource = fs.readFileSync("./SharePointOnlinePrivateKey.pem");
  const privateKeyObject = crypto.createPrivateKey({
      key: privateKeySource,
      passphrase: privateKeyPassPhrase,
      format: "pem",
  });

  const privateKey = privateKeyObject.export({
      format: "pem",
      type: "pkcs8",
  });
  
  const config = {
    auth: {
      clientId: clientId,
      authority: `https://login.microsoftonline.com/${tenantId}`,
      clientCertificate: {
        thumbprint: thumbprint, // a 40-digit hexadecimal string
        privateKey: privateKey,
      },
    },
  };

  // Create msal application object
  const cca = new msal.ConfidentialClientApplication(config);
  const helper = {
    createNewGuid:uuid
  }
  const issuer = clientId;
  const jwtAudience = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
  return cca.clientAssertion.getJwt(helper, issuer, jwtAudience);
}

async function getAccessToken() {
  // see https://github.com/SharePoint/sp-dev-docs/issues/5889 and https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate
  try {
    const response = await axios.post(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, new URLSearchParams({
      client_id: clientId,
      scope: `${resource}/.default`,
      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      client_assertion: getClientAssertion(),
      grant_type: 'client_credentials',
    }), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data.access_token;
  } catch (error) {
    console.error('Error getting access token:', error);
    throw error;
  }
}

// run the script
getAccessToken()
.then(token => {
  return axios.get(`https://mycompany.sharepoint.com/sites/contoso/Toolbox/Demo/_api/web/lists/getbytitle('Assets')/items(123)?$select=Title`, {
    headers:{
      'Authorization':'Bearer '+token,
      'Accept': 'application/json; odata=verbose'
    }
  })
})
.then(response => {
  console.log(response.data)
})
.catch(console.log)
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2024/04/29/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bearer-token/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Supprimer la commande Ctrl+Alt+Supp pour ouvrir Windows 11</title>
		<link>https://blog.kodono.info/wordpress/2024/01/02/supprimer-la-commande-ctrlaltsupp-pour-ouvrir-windows-11/</link>
					<comments>https://blog.kodono.info/wordpress/2024/01/02/supprimer-la-commande-ctrlaltsupp-pour-ouvrir-windows-11/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Tue, 02 Jan 2024 08:01:34 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[Français]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[France]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2275</guid>

					<description><![CDATA[Il suffit de modifier le registre de Windows (regedit), en passant à 1 la variable DisableCad dans ces deux emplacements : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]]></description>
										<content:encoded><![CDATA[<p>Il suffit de modifier le registre de Windows (<code>regedit</code>), en passant à <b>1</b> la variable <b>DisableCad</b> dans ces deux emplacements :</p>
<ul>
<li>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System</li>
<li>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2024/01/02/supprimer-la-commande-ctrlaltsupp-pour-ouvrir-windows-11/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Search and restore an item from a SharePoint Online Recycle Bin</title>
		<link>https://blog.kodono.info/wordpress/2023/11/07/search-and-restore-an-item-from-a-sharepoint-online-recycle-bin/</link>
					<comments>https://blog.kodono.info/wordpress/2023/11/07/search-and-restore-an-item-from-a-sharepoint-online-recycle-bin/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Tue, 07 Nov 2023 11:09:37 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[sharepoint]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2267</guid>

					<description><![CDATA[It might be difficult to search for an item in a SharePoint recycle bin. I was using the end point _api/site/RecycleBin as a Site Collection Administrator, but in some cases it returns an error &#8220;The attempted operation is prohibited because it exceeds the list view threshold.&#8221;. The solution is to use another end point _api/site/getrecyclebinitems [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>It might be difficult to search for an item in a SharePoint recycle bin. I was using the end point <code>_api/site/RecycleBin</code> as a <b>Site Collection Administrator</b>, but in some cases it returns an error &#8220;The attempted operation is prohibited because it exceeds the list view threshold.&#8221;.</p>
<p>The solution is to use another end point <code>_api/site/getrecyclebinitems</code> (<a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.sharepoint.client.site.getrecyclebinitems?view=sharepoint-csom">see the documentation</a> for the various parameters):</p>
<pre class="brush:javascript">
fetch("https://tenant.sharepoint.com/sites/YourSite/_api/site/getrecyclebinitems?rowLimit=%2770000%27&#038;itemState=0&#038;orderby=3&#038;isAscending=false", {
  "headers": {
    "accept": "application/json",
    "content-type": "application/json",
    "x-requestdigest": document.querySelector("#__REQUESTDIGEST").value
  },
  "method": "GET",
})
.then(res => res.json())
.then(json => {
  console.log(json.value);
  // it will show an array with an object that contains several interesting properties:
  // AuthorEmail, AuthorName, DeletedByEmail, DeletedDate, DirName, Id, LeafName, Title, …
})
</pre>
<p>You can then filter the result to get what you need, for example if you&#8217;re looking for an item from the list “Projects” that has the ID 1981:</p>
<pre class="brush:javascript">
json.value.filter(item => item.DirName.includes("Lists/Projects") &#038;& item.LeafName === "1981_.000");
</pre>
<p>Then, to restore an item, we need the <code>Id</code> from the previous result. The famous endpoint to restore an item is <code>_api/site/RecycleBin('Id')/restore</code>, however it could also return the error &#8220;The attempted operation is prohibited because it exceeds the list view threshold&#8221;. In that case, we can use this other endpoint <code>_api/site/RecycleBin/RestoreByIds</code>:</p>
<pre class="brush:javascript">
fetch("https://tenant.sharepoint.com/sites/YourSite/_api/site/RecycleBin/RestoreByIds", {
  "headers": {
    "accept": "application/json",
    "content-type": "application/json",
    "x-requestdigest": document.querySelector("#__REQUESTDIGEST").value
  },
  "method": "POST",
  "body":JSON.stringify({
    "ids": [
      "4f855ee7-472b-414a-a482-4317a114c1a2" // Id to restore
    ],
    "bRenameExistingItems": true
  })
})
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2023/11/07/search-and-restore-an-item-from-a-sharepoint-online-recycle-bin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Calculating HMAC SHA-1 in the Browser</title>
		<link>https://blog.kodono.info/wordpress/2023/04/16/calculating-hmac-sha-1-in-the-browser/</link>
					<comments>https://blog.kodono.info/wordpress/2023/04/16/calculating-hmac-sha-1-in-the-browser/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sun, 16 Apr 2023 17:23:55 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2258</guid>

					<description><![CDATA[If you&#8217;re looking for the equivalent of hash_hmac('sha1', 'string', 'secret'); in JavaScript, then here you go: async function hmac_sha1 (str, secret) { // see https://stackoverflow.com/a/47332317/1134119 let enc = new TextEncoder("utf-8"); let key = await window.crypto.subtle.importKey( "raw", // raw format of the key - should be Uint8Array enc.encode(secret), { // algorithm details name: "HMAC", hash: {name: [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>If you&#8217;re looking for the equivalent of <code>hash_hmac('sha1', 'string', 'secret');</code> in JavaScript, then here you go:</p>
<pre class="brush:js">
async function hmac_sha1 (str, secret) {
  // see https://stackoverflow.com/a/47332317/1134119
  let enc = new TextEncoder("utf-8");
  let key = await window.crypto.subtle.importKey(
    "raw", // raw format of the key - should be Uint8Array
    enc.encode(secret),
    { // algorithm details
      name: "HMAC",
      hash: {name: "SHA-1"}
    },
    false, // export = false
    ["sign", "verify"] // what this key can do
  );
  let signature = await window.crypto.subtle.sign(
    "HMAC",
    key,
    enc.encode(str)
  );
  let b = new Uint8Array(signature);
  return Array.prototype.map.call(b, x => x.toString(16).padStart(2, '0')).join("");
}
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2023/04/16/calculating-hmac-sha-1-in-the-browser/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Deploy a PCF NodeJS app as a scheduled task</title>
		<link>https://blog.kodono.info/wordpress/2022/12/27/deploy-a-pcf-nodejs-app-as-a-scheduled-task/</link>
					<comments>https://blog.kodono.info/wordpress/2022/12/27/deploy-a-pcf-nodejs-app-as-a-scheduled-task/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Tue, 27 Dec 2022 15:23:33 +0000</pubDate>
				<category><![CDATA[Application]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2222</guid>

					<description><![CDATA[I have a NodeJS app that runs as a process and that executes a task every 15 minutes using node-schedule. We first need a manifest.yml file that contains: --- applications: - name: APP-NAME buildpack: nodejs_buildpack no-route: true health-check-type: process env: OPTIMIZE_MEMORY: true The no-route parameter is true so that we don&#8217;t get a route assigned, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I have a NodeJS app that runs as a process and that executes a task every 15 minutes using <code>node-schedule</code>.</p>
<p>We first need a <b>manifest.yml</b> file that contains:</p>
<pre>
---
applications:
- name: APP-NAME
  buildpack: nodejs_buildpack
  no-route: true
  health-check-type: process
  env:
    OPTIMIZE_MEMORY: true
</pre>
<p>The <code>no-route</code> parameter is <b>true</b> so that we don&#8217;t get a route assigned, and the <code>health-check-type</code> is set to <b>process</b> so that the orchestrator monitors process availability and doesn&#8217;t try to ping a non-existent web endpoint. And <code>OPTIMIZE_MEMORY</code> in <a href="https://docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#env-block">&#8220;env&#8221; section</a> is based on the <a href="https://docs.cloudfoundry.org/buildpacks/node/node-tips.html">Pivotal recommendations</a>.</p>
<p>If you need to use a local package in your app, you&#8217;ll have to pack it up first. To do it, go to your local module folder, and type <code>npm pack</code>. It will create a <code>.tgz</code> file that you&#8217;ll have to store in a <b>local_modules</b> folder for your app. Next, use <code>npm install .\local_modules\package-1.2.3.tgz</code>.</p>
<p>You can now deploy your app with <code>pcf push APP-NAME</code> and you can read the logs with <code>cf logs APP-NAME --recent</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2022/12/27/deploy-a-pcf-nodejs-app-as-a-scheduled-task/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Power Automate: execute a SQL Query via On-Promise Gateway</title>
		<link>https://blog.kodono.info/wordpress/2022/12/09/power-automate-execute-a-sql-query-via-on-promise-gateway/</link>
					<comments>https://blog.kodono.info/wordpress/2022/12/09/power-automate-execute-a-sql-query-via-on-promise-gateway/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Fri, 09 Dec 2022 13:28:26 +0000</pubDate>
				<category><![CDATA[Divers]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2201</guid>

					<description><![CDATA[In Power Automate, when you want to connect to a SQL Server and if you have a On-Promise Gateway, then you cannot use the command &#8220;Execute a SQL Query&#8221; because it will say it&#8217;s not currently supported. There is a workaround with &#8220;Transform data using Power Query&#8221; (ATTENTION: you cannot load it from a flow [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>In Power Automate, when you want to connect to a SQL Server and if you have a On-Promise Gateway, then you cannot use the command <b>&#8220;Execute a SQL Query&#8221;</b> because it will say it&#8217;s not currently supported.</p>
<p>There is a workaround with <b>&#8220;Transform data using Power Query&#8221;</b> (ATTENTION: you cannot load it from a flow from a Solution… you&#8217;ll have to go to your Flows and edit the flow from there):<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/sql_options.png"><img fetchpriority="high" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/sql_options.png" alt="" width="603" height="669" class="aligncenter size-full wp-image-2203" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/sql_options.png 603w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/sql_options-270x300.png 270w" sizes="(max-width: 603px) 100vw, 603px" /></a></p>
<p>Let&#8217;s say we have 3 tables: ITEM_CATALOG, CATALOG and CURRENCY. We want to join them and filter them based on a variable found previously in our flow.</p>
<p>First, we can define our <code>where</code>. Here I have several values that I want to test using a <code>IN</code>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/init_where.png"><img decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/init_where.png" alt="" width="605" height="178" class="aligncenter size-full wp-image-2205" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/init_where.png 605w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/init_where-300x88.png 300w" sizes="(max-width: 605px) 100vw, 605px" /></a></p>
<p>I create a string with my different values separated by a coma.</p>
<p>Next, we can open the Power Query editor:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/power_query.png"><img decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/power_query.png" alt="" width="619" height="128" class="aligncenter size-full wp-image-2206" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/power_query.png 619w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/power_query-300x62.png 300w" sizes="(max-width: 619px) 100vw, 619px" /></a></p>
<p>In the interface, we choose the 3 tables we need to merge and we add a parameter called &#8220;where&#8221;:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/new_parameter.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/new_parameter.png" alt="" width="250" height="352" class="aligncenter size-full wp-image-2207" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/new_parameter.png 250w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/new_parameter-213x300.png 213w" sizes="auto, (max-width: 250px) 100vw, 250px" /></a></p>
<p>We rename it to &#8220;where&#8221; and leave the default settings:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parameter_menu.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parameter_menu.png" alt="" width="652" height="637" class="aligncenter size-full wp-image-2208" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parameter_menu.png 652w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parameter_menu-300x293.png 300w" sizes="auto, (max-width: 652px) 100vw, 652px" /></a></p>
<p>Then we use the <b>&#8220;Advance Editor&#8221;</b>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/advanced_editor.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/advanced_editor.png" alt="" width="805" height="318" class="aligncenter size-full wp-image-2209" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/advanced_editor.png 805w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/advanced_editor-300x119.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/advanced_editor-768x303.png 768w" sizes="auto, (max-width: 805px) 100vw, 805px" /></a></p>
<p>And we wrote the below:</p>
<pre class="brush:javascript">
let
  where = Text.Split( "@{variables('where')}" , ",")
in
  where
</pre>
<p>It means we want to split the variable &#8220;where&#8221; coming from the flow, based on the coma separator:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/editor_value.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/editor_value.png" alt="" width="520" height="175" class="aligncenter size-full wp-image-2210" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/editor_value.png 520w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/editor_value-300x101.png 300w" sizes="auto, (max-width: 520px) 100vw, 520px" /></a></p>
<p>We can now merge the tables and add a filter:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons-1024x112.png" alt="" width="1024" height="112" class="aligncenter size-large wp-image-2211" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons-1024x112.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons-300x33.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons-768x84.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/buttons.png 1175w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>And when the step to filter is here, we select <b>&#8220;in&#8221;</b> and our query:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/filter_rows.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/filter_rows.png" alt="" width="609" height="301" class="aligncenter size-full wp-image-2212" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/filter_rows.png 609w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/filter_rows-300x148.png 300w" sizes="auto, (max-width: 609px) 100vw, 609px" /></a></p>
<p>Last step is to <b>&#8220;Enable Load&#8221;</b> to make sure this is what the operation will return to our flow:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/enable_load.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/enable_load.png" alt="" width="307" height="437" class="aligncenter size-full wp-image-2213" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/enable_load.png 307w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/enable_load-211x300.png 211w" sizes="auto, (max-width: 307px) 100vw, 307px" /></a></p>
<p>You can run it to test and see if it works.</p>
<p>Then, to get the output from it, we&#8217;ll use a <b>&#8220;Parse JSON&#8221;</b>… The schema is probably something like:</p>
<pre class="brush:json">
{
    "type": "object",
    "properties": {
        "resultType": {
            "type": "string"
        },
        "value": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "COLUMN_A": {
                        "type": "string"
                    },
                    "COLUMN_B": {
                        "type": "integer"
                    },
                    "COLUMN_C": {
                        "type": "string"
                    }
                },
                "required": [
                    "COLUMN_A",
                    "COLUMN_B",
                    "COLUMN_C"
                ]
            }
        }
    }
}
</pre>
<p>You may need to make several tries in order to find the correct schema. You can also use the <b>&#8220;Generate from sample&#8221;</b> by pasting the data from the previous step:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parse_json.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parse_json.png" alt="" width="629" height="437" class="aligncenter size-full wp-image-2214" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parse_json.png 629w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/parse_json-300x208.png 300w" sizes="auto, (max-width: 629px) 100vw, 629px" /></a></p>
<p>We use <b>&#8220;value&#8221;</b> in the loop:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/value.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/value.png" alt="" width="667" height="378" class="aligncenter size-full wp-image-2215" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/value.png 667w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/value-300x170.png 300w" sizes="auto, (max-width: 667px) 100vw, 667px" /></a></p>
<p>And then we can access our columns:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/applyeach.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/applyeach.png" alt="" width="670" height="376" class="aligncenter size-full wp-image-2216" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/applyeach.png 670w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/12/applyeach-300x168.png 300w" sizes="auto, (max-width: 670px) 100vw, 670px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2022/12/09/power-automate-execute-a-sql-query-via-on-promise-gateway/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Pass an URL parameter to a SharePoint Online form&#8217;s field</title>
		<link>https://blog.kodono.info/wordpress/2022/11/30/pass-an-url-parameter-to-a-sharepoint-online-forms-field/</link>
					<comments>https://blog.kodono.info/wordpress/2022/11/30/pass-an-url-parameter-to-a-sharepoint-online-forms-field/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Wed, 30 Nov 2022 08:43:25 +0000</pubDate>
				<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[sharepoint]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2170</guid>

					<description><![CDATA[The only way to pass a URL parameter to a SharePoint Online (modern design) form&#8217;s field is to use PowerApps (at least, if you cannot add any JS on your website!). Important warning: when you use PowerApps to manage your form, all edits to the list settings won&#8217;t reflect to the PowerApps form. For example, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>The only way to pass a URL parameter to a SharePoint Online (modern design) form&#8217;s field is to use PowerApps (at least, if you cannot add any JS on your website!).</p>
<p><b style="color:red">Important warning</b>: when you use PowerApps to manage your form, all edits to the list settings won&#8217;t reflect to the PowerApps form. For example, if you add a field, it won&#8217;t show up, and you&#8217;ll have to manually update the PowerApps to add it (see at the bottom of this article).</p>
<p>From the list view, go to <code>Integrate</code> then <code>PowerApps</code> and <code>Customize forms</code>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/integrate.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/integrate.png" alt="" width="931" height="161" class="aligncenter size-full wp-image-2172" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/integrate.png 931w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/integrate-300x52.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/integrate-768x133.png 768w" sizes="auto, (max-width: 931px) 100vw, 931px" /></a></p>
<p>Once PowerApps has open the form, you need to do several things.</p>
<h2>1. Load the ID</h2>
<p>We first need to make sure the form will load the required item when we pass the <code>ID</code> URL parameter:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item-1024x442.png" alt="" width="1024" height="442" class="aligncenter size-large wp-image-2175" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item-1024x442.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item-300x130.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item-768x332.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item-1536x663.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_mode_item.png 1899w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>From the <b>SharepointForm Advanced Settings</b>, we change the <b>DefaultMode</b> to check if we have the <code>ID</code> parameter, and if we don&#8217;t have it, then it should be a New Form, otherwise an Edit Form:</p>
<pre class="brush:powershell">If(IsBlank(Param("ID")), FormMode.New, FormMode.Edit)</pre>
<p>From the <b>SharepointForm Advanced Settings</b>, we change the <b>Item</b> section to check if we have the <code>ID</code> parameter, and if we have it, then we do a lookup in our list to find the data:</p>
<pre class="brush:powershell">If(IsBlank(Param("ID")), SharePointIntegration.Selected, LookUp('NAME OF THE LIST', ID = Value(Param("ID"))))</pre>
<h2>Add a SUBMIT button</h2>
<p>With PowerApps, there is no button to save the changes! We&#8217;ll add a button in the form:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn-1024x274.png" alt="" width="1024" height="274" class="aligncenter size-large wp-image-2178" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn-1024x274.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn-300x80.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn-768x206.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn-1536x411.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/save_btn.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>In the button&#8217;s properties, we change the <b>onSelect</b> to be:</p>
<pre class="brush:powershell">SubmitForm(SharePointForm1)</pre>
<p>Be aware that the page will stay with the form after clicking on the button. You could want to close using the <a href="https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-exit">Exit()</a> function, but the user will be redirected on office.com … I&#8217;d recommend to use <a href="https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-param">Launch()</a> by redirecting your users to a page:</p>
<pre class="brush:powershell">SubmitForm(SharePointForm1); Launch("https://tenant.sharepoint.com/sites/MySite/");</pre>
<h2>Set field&#8217;s value based on URL parameter</h2>
<p>We can finally set the field&#8217;s value based on the parameter in the URL. Select the INPUT zone of the field, and in the <b>Default</b> section we use the below formula:</p>
<pre class="brush:powershell">If(IsBlank(Param("Title")), ThisItem.Title, Param("Title"))</pre>
<p><a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value-1024x281.png" alt="" width="1024" height="281" class="aligncenter size-large wp-image-2179" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value-1024x281.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value-300x82.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value-768x211.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value-1536x422.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/title_value.png 1903w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>Here my field is called &#8220;Title&#8221; so I decided to use a parameter called &#8220;Title&#8221; as well.</p>
<h2>Link to the form</h2>
<p>We cannot use the <b>NewForm.aspx</b> or <b>EditForm.aspx</b> to access this form, but we need a special link.</p>
<p>Go to your <b>list settings</b>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/list_settings.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/list_settings.png" alt="" width="934" height="363" class="aligncenter size-full wp-image-2180" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/list_settings.png 934w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/list_settings-300x117.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/list_settings-768x298.png 768w" sizes="auto, (max-width: 934px) 100vw, 934px" /></a></p>
<p>Then go to the <b>form settings</b> (it&#8217;s from there that you can decide to keep PowerApps or use the original Sharepoint Forms), and click on <b>See versions and usage</b>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_settings.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_settings.png" alt="" width="980" height="274" class="aligncenter size-full wp-image-2181" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_settings.png 980w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_settings-300x84.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/form_settings-768x215.png 768w" sizes="auto, (max-width: 980px) 100vw, 980px" /></a></p>
<p>You&#8217;ll get the <b>App Id</b> from this page:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/app_id.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/app_id.png" alt="" width="273" height="166" class="aligncenter size-full wp-image-2182" /></a></p>
<p>Next, you&#8217;ll use the <b>App Id</b> to forge the URL: <b>https://apps.powerapps.com/play/providers/Microsoft.PowerApps/apps/APP_ID</b><br />
With our example, the URL will be: <a href="https://apps.powerapps.com/play/providers/Microsoft.PowerApps/apps/c6f23ac1-dcbd-4daf-925e-2701ab241ca0">https://apps.powerapps.com/play/providers/Microsoft.PowerApps/apps/c6f23ac1-dcbd-4daf-925e-2701ab241ca0</a></p>
<p>You can now pass the URL parameter: <b>https://apps.powerapps.com/play/providers/Microsoft.PowerApps/apps/APP_ID?Title=Hello%20World</b><br />
And an ID to retrieve an existing item: <b>https://apps.powerapps.com/play/providers/Microsoft.PowerApps/apps/APP_ID?Title=Hello%20World&#038;ID=2</b></p>
<h2>How to use it with a LookUp column?</h2>
<p>If you want to auto-select a LookUp field using an URL parameter, you need to do a few things…</p>
<p>First, we need to add the related table. To do so, click on <b>Data</b> in the left navigation bar and search for <b>SharePoint</b>:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/datasource_sharepoint.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/datasource_sharepoint.png" alt="" width="351" height="512" class="aligncenter size-full wp-image-2188" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/datasource_sharepoint.png 351w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/datasource_sharepoint-206x300.png 206w" sizes="auto, (max-width: 351px) 100vw, 351px" /></a></p>
<p>Search for the table and add it.</p>
<p>Second (optional) step: click on the Lookup field in the form and change the <b>Items</b> to show a list of options – if no &#8220;Lookup&#8221; ID in the URL, then we use the default list of options:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options-1024x218.png" alt="" width="1024" height="218" class="aligncenter size-large wp-image-2192" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options-1024x218.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options-300x64.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options-768x163.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options-1536x327.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/items_options.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>The below formula permits to retrieve the &#8220;ID&#8221; and &#8220;Title&#8221; from the distant list, base on the &#8220;Lookup&#8221; parameter, and to rename the result as <code>{Id:"ID", Value:"Title"}</code>:</p>
<pre class="brush:powershell">If(IsBlank(Param("Lookup")), Choices([@'CURRENT LIST NAME'].COLUMN_NAME), RenameColumns(ShowColumns(Filter('DISTANT LIST NAME', ID = Value(Param("Lookup"))), "ID", "Title"), "ID", "Id", "Title", "Value"))</pre>
<p>Third, click on the Lookup field in the form and change the <b>DefaultSelectedItems</b> to select the item from the list of options:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items-1024x204.png" alt="" width="1024" height="204" class="aligncenter size-large wp-image-2193" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items-1024x204.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items-300x60.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items-768x153.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items-1536x307.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/default_selected_items.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>The below formula returns an empty selection with <code>{Id:"", Value:""}</code> when no URL param, otherwise it returns the first record for our lookup:</p>
<pre class="brush:plain">If(IsBlank(Param("Lookup")), {Id:"", Value:""}, First(RenameColumns(ShowColumns(Filter('DISTANT LIST NAME', ID = Value(Param("Lookup"))), "ID", "Title"), "ID", "Id", "Title", "Value")))</pre>
<p>And finally, we can pass <b>Lookup=ID</b> in the URL to select the related item in the other list</p>
<h2>How to deal with new fields?</h2>
<p>If you add a new field to your list&#8217;s settings, you&#8217;ll have to edit the form in PowerApps, and then edit the fields and add the new one:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field.png"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field-1024x239.png" alt="" width="1024" height="239" class="aligncenter size-large wp-image-2197" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field-1024x239.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field-300x70.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field-768x179.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field-1536x359.png 1536w, https://blog.kodono.info/wordpress/wp-content/uploads/2022/11/add_field.png 1906w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></p>
<p>(I used <a href="https://www.about365.nl/2020/08/06/passing-parameters-to-your-power-apps-sharepoint-form/">this article</a> as a starting point)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2022/11/30/pass-an-url-parameter-to-a-sharepoint-online-forms-field/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Transfer an Alexa AWS Lambda function from the online editor to the ASK CLI</title>
		<link>https://blog.kodono.info/wordpress/2022/09/20/transfer-a-aws-lambda-function-from-the-online-editor-to-the-ask-cli/</link>
					<comments>https://blog.kodono.info/wordpress/2022/09/20/transfer-a-aws-lambda-function-from-the-online-editor-to-the-ask-cli/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Tue, 20 Sep 2022 17:59:44 +0000</pubDate>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2163</guid>

					<description><![CDATA[When we follow the guide to build a new smarthome skill, it gives the steps to create a function in the online code editor. But if you prefer to use the ASK CLI, there is some steps to follow… I first create a fake skill with ask new (using the &#8220;hello world&#8221; and &#8220;AWS Lambda&#8221; [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>When we follow <a href="https://developer.amazon.com/en-US/docs/alexa/smarthome/develop-smart-home-skills-in-multiple-languages.html">the guide to build a new smarthome skill</a>, it gives the steps to create a function in the online code editor.</p>
<p>But if you prefer to use the <a href="https://developer.amazon.com/en-US/docs/alexa/smapi/ask-cli-intro.html">ASK CLI</a>, there is some steps to follow…</p>
<p>I first create a fake skill with <code>ask new</code> (using the &#8220;hello world&#8221; and &#8220;AWS Lambda&#8221; options).</p>
<p>Once the folder structure and files are created, I edit the <code>.ask/ask-states.json</code> file to reflect the information from the skill I created during the guide.</p>
<p>Then in the folder <code>skill-package</code> I remove everything except <code>skill.json</code>. To find what to put into that file, use the command: <code>ask smapi get-skill-manifest -s &lt;SKILL ID&gt;</code> and copy/paste that code.</p>
<p>Finally, I force the deploy with <code>ask deploy --ignore-hash</code>.</p>
<p>The Lambda function can now be managed locally on your computer and deployed with ASK CLI. You can go to the different skill consoles to delete the fake skill &#8220;hello world&#8221; you created.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2022/09/20/transfer-a-aws-lambda-function-from-the-online-editor-to-the-ask-cli/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Debug a third party Android APK</title>
		<link>https://blog.kodono.info/wordpress/2021/11/29/debug-a-third-party-android-apk/</link>
					<comments>https://blog.kodono.info/wordpress/2021/11/29/debug-a-third-party-android-apk/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 29 Nov 2021 15:14:42 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2097</guid>

					<description><![CDATA[(inspired by this blog post) 1) Install smalidea plugin Download the smalidea plugin (see also the related Github Repository). Open up Android Studio and you should see the welcome screen like the one on screenshot below (if not, close your current project by selecting File -> Close project), go to the Plugins section, and from [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>(inspired by <a href="https://malacupa.com/2018/11/11/debug-decompiled-smali-code-in-android-studio-3.2.html">this blog post</a>)</p>
<h2>1) Install smalidea plugin</h2>
<p>Download the <a href="https://bitbucket.org/JesusFreke/smalidea/downloads/">smalidea plugin</a> (see also the related <a href="https://github.com/JesusFreke/smalidea">Github Repository</a>).</p>
<p>Open up <a href="https://developer.android.com/studio">Android Studio</a> and you should see the welcome screen like the one on screenshot below (if not, close your current project by selecting <code>File -> Close project</code>), go to the <code>Plugins</code> section, and from the wheel icon, select <code>Install Plugin from Disk...</code>. Select the smalidea plugin (ZIP file) you downloaded.<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture.png" alt="Android Studio welcome screen" width="812" height="349" class="aligncenter size-full wp-image-2098" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture.png 812w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-300x129.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-768x330.png 768w" sizes="auto, (max-width: 812px) 100vw, 812px" /></p>
<h2>2) Get the third party APK</h2>
<p>You first need <strong>to know the type of platform</strong> where you&#8217;ll do your debug tests. To do so, make sure <b>your device is connected to your computer</b> (it could also be a virtual device started from the AVD Manager) with <code>adb devices</code>.<br />
Then, use the command <code>adb shell getprop ro.product.cpu.abi</code> to find the type of processor you have. When I use my phone, I got <b>arm64-v8a</b>.</p>
<p>Go to an APK platform, like <a href="https://apkcombo.com/">https://apkcombo.com/</a> and search for the Android app you want to debug. Download the <b>APK version</b> that fits to the type you found before:<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-1.png" alt="screenshot of https://apkcombo.com/" width="992" height="667" class="aligncenter size-full wp-image-2100" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-1.png 992w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-1-300x202.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-1-768x516.png 768w" sizes="auto, (max-width: 992px) 100vw, 992px" /></p>
<h2>2bis) Have a look at the APK content</h2>
<p>You can use <a href="https://github.com/skylot/jadx">JADX</a> to open the APK and have a quick look at the code.</p>
<h2>3) Decompile APK</h2>
<p>With <a href="https://ibotpeaches.github.io/Apktool/">APKTool</a>, we&#8217;ll use the command: <code>.\apktool.bat d ".\the_original_app_from_apkcombo.com.apk" -o app_to_debug</code>.<br />
A folder called <b>app_to_debug</b> is created with the decompiled version of the application.</p>
<p>Next, we need to copy the source files: <b>create a folder called &#8220;src&#8221;</b> in the new <b>app_to_debug</b> folder, and type <code>cp -R smali*/* src/</code>.</p>
<h2>4) Import project in Android Studio</h2>
<p><b>Open an existing Android Studio project</b> and select the <code>app_to_debug</code> folder where you unpacked APK.</p>
<p>Once the project loads, you need to tell the IDE where is your source code. Make sure you&#8217;re using the &#8220;Project view&#8221; in the left side panel:<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-2.png" alt="" width="201" height="143" class="aligncenter size-full wp-image-2105" /></p>
<p>Now you can see folder structure in your left panel. Find <code>src/</code> subfolder right click it and select <code>Mark Directory as -> Sources Root</code>.</p>
<h2>5) Prepare App for Debugging</h2>
<p>Open <code>AndroidManifest.xml</code> from the <code>app_to_debug</code> and find the XML element <code>&lt;application&gt;</code>. Add the attribute <code>android:debuggable</code> with value <b>&#8220;true&#8221;</b>. Example:</p>
<pre class="brush:xml">
&lt;application android:debuggable="true" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:largeHeap="true" android:name="org.horaapps.leafpic.App" android:theme="@style/Theme.AppCompat"&gt;
</pre>
<h2>6) Repack to APK</h2>
<p>You can now repack to APK with the command <code>.\apktool.bat b -d ".\app_to_debug\" -o app_unsigned.apk</code></p>
<h2>7) Sign the APK</h2>
<h3>7a) Create a keystore</h3>
<p>You first need a keystore using <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html">keytool</a> and type the below command:<br />
<code>keytool -genkeypair -v -keystore mykey.keystore -alias mykey -keyalg RSA -keysize 2048 -validity 10000</code></p>
<p>Several questions you&#8217;ll be asked, as well as a password. Make sure to remember the password for later.</p>
<h3>7b) Validate the APK</h3>
<p>You then need <code>zipalign</code> that can be found in the Android SDK folder (e.g. <em>C:\Users\USERNAME\AppData\Local\Android\Sdk\build-tools\31.0.0\zipalign.exe</em>) to validate your APK:<br />
<code>.\Path\to\Android\Sdk\build-tools\31.0.0\zipalign.exe -f -v 4 .\app_unsigned.apk .\app_ready.apk</code></p>
<h3>7c) Sign the APK</h3>
<p>Finally you can sign the new created APK with <code>apksigner</code>:<br />
<code>.\Path\to\Android\Sdk\build-tools\31.0.0\apksigner.bat sign --ks .\mykey.keystore --ks-key-alias app_to_debug --out .\app_signed.apk .\app_ready.apk</code></p>
<h2>8) Install the APK</h2>
<p>You can install it using <code>adb install app_signed.apk</code></p>
<h2>9) Prepare the host</h2>
<p>On your Android device, go to <code>Settings -> Developer options</code> and set <code>USB debugging</code> and <code>Wait for debugger options on</code>. The latter is optional but useful as it allows you wait for debugger connection and not to run app yet.</p>
<p>Finally, you should tap on <code>Select debug app</code> and choose the app you just installed. After all of these, your Developer options menu should look somewhat like this:<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-3.png" alt="" width="360" height="619" class="aligncenter size-full wp-image-2121" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-3.png 360w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-3-174x300.png 174w" sizes="auto, (max-width: 360px) 100vw, 360px" /></p>
<p>Now, <b>launch the app</b> on the Android device, and you&#8217;ll get the below message:<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-4.png" alt="" width="360" height="234" class="aligncenter size-full wp-image-2122" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-4.png 360w, https://blog.kodono.info/wordpress/wp-content/uploads/2021/11/Capture-4-300x195.png 300w" sizes="auto, (max-width: 360px) 100vw, 360px" /></p>
<h2>10) Forward debugger port</h2>
<p>You can use the adb&#8217;s port forwarding feature and forward JDWP service where application&#8217;s debug interface is listening.</p>
<p>Find the JDWP port with the command <code>adb jdwp</code>, then use this port with the command:<br />
<code>adb forward tcp:5005 jdwp:JDWP_PORT</code></p>
<h2>11) Connect Debugger</h2>
<p>Go to Android Studio and from its top menu bar choose <code>Run -> Debug…</code>, then a small message appears with one unique option that is <code>Edit Configurations...</code>. There, in the window, use a plus (+) button at the opt left, and add a new configuration of type <b>Remote</b>. Leave the default configuration as is. Click the Debug button and your app should be running with the attached debugger which means it will stop once a breakpoint is hit and you can investigate the content of app&#8217;s variables.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2021/11/29/debug-a-third-party-android-apk/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Capacitor Plugin for HTTP requests with self-signed SSL certificates</title>
		<link>https://blog.kodono.info/wordpress/2021/10/30/capacitor-plugin-for-http-requests-with-self-signed-ssl-certificates/</link>
					<comments>https://blog.kodono.info/wordpress/2021/10/30/capacitor-plugin-for-http-requests-with-self-signed-ssl-certificates/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sat, 30 Oct 2021 13:26:01 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2094</guid>

					<description><![CDATA[I&#8217;m using CapacitorJS for easy development with Android. I needed a way to do an HTTPS request to a box that uses self-signed SSL certificate. To accomplish it, I created my own capacitor plugin. See this wiki page for details: https://github.com/Aymkdn/assistant-freebox-cloud/wiki/Capacitor-Plugin-for-HTTP-requests-with-self-signed-SSL-certificates]]></description>
										<content:encoded><![CDATA[<p>I&#8217;m using CapacitorJS for easy development with Android. I needed a way to do an HTTPS request to a box that uses self-signed SSL certificate. To accomplish it, I created my own capacitor plugin.</p>
<p>See this wiki page for details: <a href="https://github.com/Aymkdn/assistant-freebox-cloud/wiki/Capacitor-Plugin-for-HTTP-requests-with-self-signed-SSL-certificates">https://github.com/Aymkdn/assistant-freebox-cloud/wiki/Capacitor-Plugin-for-HTTP-requests-with-self-signed-SSL-certificates</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2021/10/30/capacitor-plugin-for-http-requests-with-self-signed-ssl-certificates/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Let&#8217;s Encrypt Certificate: how to remove a domain from a certname that contains several domains</title>
		<link>https://blog.kodono.info/wordpress/2020/07/17/lets-encrypt-certificate-how-to-remove-a-domain-from-a-certname-that-contains-several-domains/</link>
					<comments>https://blog.kodono.info/wordpress/2020/07/17/lets-encrypt-certificate-how-to-remove-a-domain-from-a-certname-that-contains-several-domains/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Fri, 17 Jul 2020 07:18:13 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[ssl]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2019</guid>

					<description><![CDATA[My server manages several websites with different domains using Apache. The first time I used Let&#8217;s Encrypt I followed the default command which has created one certname for ALL my domains. Now I want to remove just one domain from this certificate, and it becomes complicated to understand how to do it. The best solution [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>My server manages several websites with different domains using Apache. The first time I used Let&#8217;s Encrypt I followed the default command which has created one certname for ALL my domains.</p>
<p>Now I want to remove just one domain from this certificate, and it becomes complicated to understand how to do it. The best solution is to create a new certificate for each of my domains, and then to delete the original certname.</p>
<p>Let&#8217;s say my certname is called <code>www.example.com</code> and it contains the below domains:</p>
<ul>
<li>www.example.com</li>
<li>example.com</li>
<li>blog.example.com</li>
<li>other-example.com</li>
<li>www.other-example.com</li>
<li>my-other-domain.com</li>
<li>www.my-other-domain.com</li>
<li>api.test.com</li>
</ul>
<p>The one I don&#8217;t need anymore is <code>*.my-other-domain.com</code>.</p>
<p>First, we create a certificate individually for each domain that we want to keep:</p>
<pre class="brush:python">
certbot --apache --cert-name example.com -d example.com,www.example.com,blog.example.com
certbot --apache --cert-name other-example.com -d other-example.com,www.other-example.com
certbot --apache --cert-name test.com -d api.test.com
</pre>
<p><code>--cert-name</code> permits to give our own name to the certificate, and <code>-d</code> indicates which domains should be added to this certificate.</p>
<p>Then we can list all our certificates:</p>
<pre class="brush:bash">
certbot certificates
</pre>
<p>Using the above command you can find the <code>Certificate Path</code> and now we can delete our original certificate:</p>
<pre class="brush:bash">
certbot revoke --cert-path /etc/letsencrypt/live/www.example.com/fullchain.pem
</pre>
<p>You&#8217;re all set! All your domains should still have a correct certificate, and you revoked the ones you don&#8217;t need anymore.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2020/07/17/lets-encrypt-certificate-how-to-remove-a-domain-from-a-certname-that-contains-several-domains/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Upgrade from MySQL (5.6.40 – Debian 9.12) to MariaDB</title>
		<link>https://blog.kodono.info/wordpress/2020/04/05/upgrade-from-mysql-5-6-40-debian-9-12-to-mariadb/</link>
					<comments>https://blog.kodono.info/wordpress/2020/04/05/upgrade-from-mysql-5-6-40-debian-9-12-to-mariadb/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sun, 05 Apr 2020 16:24:43 +0000</pubDate>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=2016</guid>

					<description><![CDATA[(Source) A few steps: sudo apt-get install software-properties-common dirmngr wget -qO - https://mariadb.org/mariadb_release_signing_key.asc &#124; sudo apt-key add - nano /etc/apt/sources.list.d/mariadb.list In mariadb.list we add the two below lines: deb [arch=amd64,i386,ppc64el] http://mirror.23media.de/mariadb/repo/10.4/debian stretch main deb-src http://mirror.23media.de/mariadb/repo/10.4/debian stretch main Then: apt-get update apt-get install mariadb-server]]></description>
										<content:encoded><![CDATA[<p>(<a href="https://downloads.mariadb.org/mariadb/repositories/#distro=Debian&#038;distro_release=stretch--stretch&#038;mirror=23Media&#038;version=10.4">Source</a>)</p>
<p>A few steps:</p>
<pre class="brush:bash">
sudo apt-get install software-properties-common dirmngr
wget -qO - https://mariadb.org/mariadb_release_signing_key.asc | sudo apt-key add -
nano /etc/apt/sources.list.d/mariadb.list
</pre>
<p>In <code>mariadb.list</code> we add the two below lines:</p>
<pre class="brush:bash">
deb [arch=amd64,i386,ppc64el] http://mirror.23media.de/mariadb/repo/10.4/debian stretch main
deb-src http://mirror.23media.de/mariadb/repo/10.4/debian stretch main
</pre>
<p>Then:</p>
<pre class="brush:bash">
apt-get update
apt-get install mariadb-server
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2020/04/05/upgrade-from-mysql-5-6-40-debian-9-12-to-mariadb/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sharepoint REST API to index a column or delete a column and more&#8230;</title>
		<link>https://blog.kodono.info/wordpress/2019/08/19/sharepoint-rest-api-to-index-a-column-or-delete-a-column-and-more/</link>
					<comments>https://blog.kodono.info/wordpress/2019/08/19/sharepoint-rest-api-to-index-a-column-or-delete-a-column-and-more/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 19 Aug 2019 09:16:50 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[sharepoint]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1979</guid>

					<description><![CDATA[Due to the 5,000 items threshold limitation it can become very frustrating to administrate Sharepoint. For example, if your list has more than 5,000 items, you cannot add an index, delete a lookup/people column, delete the list or the website, and more ! Hopefully some of the operations can be done with REST API, and [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Due to the 5,000 items threshold limitation it can become very frustrating to administrate Sharepoint. For example, if your list has more than 5,000 items, you cannot add an index, delete a lookup/people column, delete the list or the website, and more !</p>
<p>Hopefully some of the operations can be done with REST API, and in my organization the threshold limitation is removed during the night (1am to 4am). By combining a schedule service (like <a href="https://github.com/node-schedule/node-schedule">node-schedule</a>) with my library <a href="https://aymkdn.github.io/SharepointPlus/">SharepointPlus</a> I can run this admin tasks during the night while my virtual computer is running.</p>
<h2>To delete a column</h2>
<pre class="brush:javascript">
$SP().ajax({
  url:"https://company.com/sharepoint/team/sales/_api/web/lists/getbytitle('My List')/fields/getbytitle('Column To Delete')",
  method: "POST",
  headers: {
    "IF-MATCH": "*",
    "X-HTTP-Method": "DELETE"
  }
});
</pre>
<h2>To index a column</h2>
<pre class="brush:javascript">
$SP().ajax({
  url:"https://company.com/sharepoint/team/sales/_api/web/lists/getbytitle('My List')/fields/getbytitle('Column to Index')",
  method: "POST",
  body:JSON.stringify({
    "__metadata":{ type: "SP.Field" },
    "Indexed":true
  }),
  headers: {
    "IF-MATCH": "*",
    "X-HTTP-Method": "MERGE"
  }
})
</pre>
<h2>To delete a list</h2>
<pre class="brush:javascript">
$SP().ajax({
  url:"https://company.com/sharepoint/team/sales/_api/web/lists/getbytitle('List To Delete')",
  method: "POST",
  headers: {
    "IF-MATCH": "*",
    "X-HTTP-Method": "DELETE"
  }
})
</pre>
<h2>To delete a list</h2>
<pre class="brush:javascript">
$SP().ajax({
  url:"https://company.com/sharepoint/team/sales/_api/web/lists/getbytitle('List To Delete')",
  method: "POST",
  headers: {
    "IF-MATCH": "*",
    "X-HTTP-Method": "DELETE"
  }
})
</pre>
<h2>To delete a site</h2>
<pre class="brush:javascript">
$SP().ajax({
  url:"https://company.com/sharepoint/team/sales/website_to_delete/_api/web/",
  method: "POST",
  headers: {
    "X-HTTP-Method": "DELETE"
  }
})
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2019/08/19/sharepoint-rest-api-to-index-a-column-or-delete-a-column-and-more/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Create a self-signed certificate for localhost testing with IE11 and Webpack</title>
		<link>https://blog.kodono.info/wordpress/2018/08/23/create-a-self-signed-certificate-for-localhost-testing-with-ie11-and-webpack/</link>
					<comments>https://blog.kodono.info/wordpress/2018/08/23/create-a-self-signed-certificate-for-localhost-testing-with-ie11-and-webpack/#comments</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Thu, 23 Aug 2018 10:41:50 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[internet explorer]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1912</guid>

					<description><![CDATA[Sources: https://stackoverflow.com/questions/44988163/create-self-signed-certificate-for-testing-localhost-and-have-it-accepted-by-the http://woshub.com/how-to-create-self-signed-certificate-with-powershell/ https://stackoverflow.com/questions/4691699/how-to-convert-crt-to-pem/4691749#4691749 https://webpack.js.org/configuration/dev-server/#devserver-https If you develop with Webpack under Windows and you want to open your localhost server in HTTPS with IE11 you may receive a message like : &#8220;Content was blocked because it was not signed by a valid security certificate.&#8221; Below I explain the steps to make it work with IE11: [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Sources: </p>
<ul>
<li><a href="https://stackoverflow.com/questions/44988163/create-self-signed-certificate-for-testing-localhost-and-have-it-accepted-by-the">https://stackoverflow.com/questions/44988163/create-self-signed-certificate-for-testing-localhost-and-have-it-accepted-by-the</a></li>
<li><a href="http://woshub.com/how-to-create-self-signed-certificate-with-powershell/">http://woshub.com/how-to-create-self-signed-certificate-with-powershell/</a></li>
<li><a href="https://stackoverflow.com/questions/4691699/how-to-convert-crt-to-pem/4691749#4691749">https://stackoverflow.com/questions/4691699/how-to-convert-crt-to-pem/4691749#4691749</a></li>
<li><a href="https://webpack.js.org/configuration/dev-server/#devserver-https">https://webpack.js.org/configuration/dev-server/#devserver-https</a></li>
</ul>
<p>If you develop with Webpack under Windows and you want to open your localhost server in HTTPS with IE11 you may receive a message like :</p>
<blockquote><p>&#8220;Content was blocked because it was not signed by a valid security certificate.&#8221;</p></blockquote>
<p>Below I explain the steps to make it work with IE11:</p>
<ol>
<li>Open <strong>Powershell in Administrator mode</strong></li>
<li>Type (no need to change anything, just copy/paste):
<pre class="brush:bash">New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname localhost -FriendlyName "Dev localhost" -NotAfter (Get-Date).AddMonths(240) -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")</pre>
<p>It should return a <strong>Thumbprint</strong>:</p>
<pre class="brush:bash">   PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\my

Thumbprint                                Subject
----------                                -------
1D97DFF290FBACE0871F16AD1B38172F253CA048  CN=localhost</pre>
<p>You may want to open <code>certlm.msc</code> to see the new certificate in Personal</li>
<li><strong>Export to .cer</strong> with the below Powershell command (make sure to <strong>replace</strong> <code>1D97DFF290FBACE0871F16AD1B38172F253CA048</code> with the <strong>Thumbprint</strong> you received before)&#8230; it will be exported to <code>C:\localhost.cert</code> (but you can change the path):
<pre class="brush:bash">Export-Certificate -Cert Cert:\LocalMachine\My\1D97DFF290FBACE0871F16AD1B38172F253CA048 -FilePath C:\localhost.cer (example: Export-Certificate -Cert Cert:\LocalMachine\My\PREVIOUS_Thumbprint_HERE -FilePath C:\localhost.cer)</pre>
</li>
<li>We now need to <strong>export to .pfx</strong> with the below Powershell commands:
<pre class="brush:bash">
$CertPassword = ConvertTo-SecureString -String "YourPassword" -Force –AsPlainText
Export-PfxCertificate -Cert cert:\LocalMachine\My\2779C7928D055B21AAA0Cfe2F6BE1A5C2CA83B30 -FilePath C:\localhost.pfx -Password $CertPassword
</pre>
</li>
<li>Next we&#8217;ll use the opensource application called <strong>XCA</strong>: <a href="https://hohnstaedt.de/xca/">download</a> and install it.</li>
<li>Open XCA and go to : <code>File > New Database</code></li>
<li>Choose where you want to have this database and provide a password (to make it easy, you can choose the same &#8220;YourPassword&#8221; than before)</li>
<li>Go to tab <strong>Certificates</strong></li>
<li>Click <strong>Import</strong> and select the previous created <strong>localhost.cer</strong></li>
<li>Once imported, right click the cert and <strong>Export > File</strong>, then <strong>select &#8220;*.crt&#8221;</strong> for the file format</li>
<li>Go to the <strong>Private Keys</strong> tab and click on <strong>Import PFX</strong></li>
<li>Select the previously created PFX and click <strong>Import All</strong></li>
<li>Right click on &#8220;localhost_key&#8221; and <strong>Export > File</strong>, then <strong>select &#8220;PEM Private (*.pem)&#8221;</strong> for the file format</li>
<li>Close XCA and go to the folder with all your files</li>
<li>Double-click on the file <code>localhost.crt</code> you have just created</li>
<li>Windows shows a popup with a <code>"Install Certificate..."</code> button; Click on this button</li>
<li>Choose Current User, and next, click on <strong>&#8220;Place all certificates in the following store&#8221;</strong>, and browse to <strong>&#8220;Trusted Root Certification Authorities&#8221;</strong></li>
<li>Finally, in your Webpack config, you should enter something like the below:
<pre class="brush:javascript">
module.exports = {
  //...
  devServer: {
    https: {
      key: fs.readFileSync('C:\\localhost.pem'),
      cert: fs.readFileSync('C:\\localhost.crt')
    }
  }
};
</pre>
</li>
</ol>
<p>Launch your webpack server and open IE to your localhost server. It should work&#8230;</p>
<p>If you need to do <strong>CORS request</strong>, you may need to open <strong>IE settings</strong>, then go to <strong>Security</strong> and make sure to add localhost in the <strong>Local Intranet/Trusted Sites</strong>, then click on <strong>&#8220;Custom Level&#8230;&#8221;</strong>, go to <strong>&#8220;Miscellaneous&#8221;</strong> and select &#8220;Enable&#8221; for <strong>&#8220;Access data sources across domains&#8221;</strong>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2018/08/23/create-a-self-signed-certificate-for-localhost-testing-with-ie11-and-webpack/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Remove custom properties/metadata for an Office document [javascript]</title>
		<link>https://blog.kodono.info/wordpress/2018/04/19/remove-custom-properties-metadata-for-an-office-document-javascript/</link>
					<comments>https://blog.kodono.info/wordpress/2018/04/19/remove-custom-properties-metadata-for-an-office-document-javascript/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Thu, 19 Apr 2018 17:13:30 +0000</pubDate>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1891</guid>

					<description><![CDATA[I have this document library on Sharepoint where I have one custom column called Metadata and that is a lookup to another list on my Sharepoint. When the users download an Office document from this library and then re-upload it we could have the below error message: There is at least one lookup column that [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I have this document library on Sharepoint where I have one custom column called <em>Metadata</em> and that is a lookup to another list on my Sharepoint.</p>
<p>When the users download an Office document from this library and then re-upload it we could have the below error message:</p>
<blockquote><p>There is at least one lookup column that enforces a relationship behavior and contains values that reference one or more non-existent items in the target list.</p></blockquote>
<p>It&#8217;s because the Office documents keep the custom columns from the document library from where they have been downloaded&#8230; In that case the file tries to reassign the <em>Metadata</em> with an ID that doesn&#8217;t exist anymore&#8230; causing this issue!</p>
<p>Because I&#8217;m using a homemade interface to upload documents, I&#8217;ve been able to pass some code to delete the file&#8217;s properties on-the-fly before pushing it into the document library.</p>
<p>To do so, you need <a href="https://stuk.github.io/jszip/">JSZip</a> that will permit to unzip the Office document in order to retrieve the file <b>docProps/custom.xml</b> and to change the properties we want before the final upload.</p>
<p>Let&#8217;s imagine my page contains an <code>&lt;input type="file"&gt;</code>, and that I have already loaded JSZip. Then I use <a href="https://developer.mozilla.org/fr/docs/Web/API/FileReader/readAsArrayBuffer">FileReader</a>:</p>
<pre class="brush:javascript">
var fileReader = new FileReader();
fileReader.onloadend = function(e) {
  // content is the "arraybuffer" that represents my file
  var content = e.target.result;
  // check the file's extension (here "docx", "xlsx", and "pptx", but we could add more extensions
  var ext=file.name.split(".").slice(-1)[0];
  switch (ext) {
    case "docx":
    case "xlsx":
    case "pptx": {
      // load content in JSZip
      JSZip.loadAsync(content)
      .then(function(zip) {
        // unzip the file that contains metadata/properties
        return zip.file("docProps/custom.xml").async("text")
        .then(function(txt) {
          // replace the faulty column
          txt = txt.replace(/name="Metadata">&lt;vt:lpwstr>\d+&lt;\/vt:lpwstr>/,'name="Metadata">&lt;vt:lpwstr>&lt;\/vt:lpwstr>');
          // reinject the new file
          zip.file("docProps/custom.xml", txt);
          return zip.generateAsync({type:"arraybuffer"})
        })
      })
      .then(function(content) {
        // do something with your content
        // for example (https://aymkdn.github.io/SharepointPlus/): $SP().list("my_list").createFile({content:content, filename:file.name})
      })
      break;
    }
    default: {
      // for the other files, treat them normally
      // for example (https://aymkdn.github.io/SharepointPlus/): $SP().list("my_list").createFile({content:content, filename:file.name})
    }
  }
}
fileReader.onerror = function(e) {
  console.error(e.target.error);
}
fileReader.readAsArrayBuffer(file);
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2018/04/19/remove-custom-properties-metadata-for-an-office-document-javascript/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to delete a document locked by another user in Sharepoint using JavaScript</title>
		<link>https://blog.kodono.info/wordpress/2017/11/20/how-to-delete-a-document-locked-by-another-user-in-sharepoint-using-javascript/</link>
					<comments>https://blog.kodono.info/wordpress/2017/11/20/how-to-delete-a-document-locked-by-another-user-in-sharepoint-using-javascript/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 20 Nov 2017 10:37:03 +0000</pubDate>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[sharepoint]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1862</guid>

					<description><![CDATA[When you open a file from Sharepoint, it will receive a short term lock that will prevent others to change some properties on the file. This protection can be useful, but also very annoying, for example when the file is not closed properly, then the lock could stay &#8220;forever&#8221;. There are many posts on the [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>When you open a file from Sharepoint, it will receive a short term lock that will prevent others to change some properties on the file.</p>
<p>This protection can be useful, but also very annoying, for example when the file is not closed properly, then the lock could stay &#8220;forever&#8221;.</p>
<p>There are <a href="https://sharepoint.stackexchange.com/questions/42999/clearing-short-term-file-lock">many</a> <a href="https://sharepoint.stackexchange.com/questions/120246/how-to-delete-a-document-locked-by-another-user-im-a-super-admin">posts</a> <a href="https://sharepoint.stackexchange.com/questions/165970/can-we-unlock-a-shortterm-lock-via-csom-for-sp2013">on</a> <a href="https://sharepoint.stackexchange.com/questions/202665/the-file-is-locked-for-exclusive-use-by-same-person-sharepoint-online">the web</a> about it.</p>
<p>I found one that has been super useful: <a href="https://pholpar.wordpress.com/2014/04/07/how-to-use-javascript-to-delete-short-term-locks-from-documents-opened-from-sharepoint/">https://pholpar.wordpress.com/2014/04/07/how-to-use-javascript-to-delete-short-term-locks-from-documents-opened-from-sharepoint/</a><br />
The author explains very well the different steps what I&#8217;m trying to summarize:</p>
<ol>
<li>Send a request to <code>_vti_bin/_vti_aut/author.dll</code> with special headers/body</li>
<li>Auth.dll will provide the <code>lockid</code></li>
<li>Send a request to <code>_vti_bin/cellstorage.svc/CellStorageService</code> with special headers/body, included the <code>lockid</code></li>
<li>The file is unlocked</li>
</ol>
<p>The code to send to CellStorageService, and provided by the author, didn&#8217;t work for me. I&#8217;ve had to use Fiddler and open the document into Office on my computer to see the kind of requests send by it to unlock a document. Based on it, I&#8217;ve re-built the code and you can find my solution below.</p>
<p>Tested on Sharepoint 2013 On-Promise only. I don&#8217;t know if this solution works for Sharepoint Online or other version.<br />
Also note that I use <code>$SP().ajax()</code> from <a href="https://aymkdn.github.io/SharepointPlus/">SharepointPlus</a>, but it&#8217;s equivalent to the <code>$.ajax</code> from jQuery.</p>
<pre class="brush:javascript">
// full path to the document
var docUrl = "https://website.com/website/Doc_Library/Test.docx";

// start by querying author.dll to find the lockid and the user who locked it
$SP().ajax({
  url: 'https://website.com/website/_vti_bin/_vti_aut/author.dll',
  headers:{
    "Content-Type": "application/x-www-form-urlencoded",
    "MIME-Version": "1.0",
    "Accept": "auth/sicily",
    "X-Vermeer-Content-Type": "application/x-www-form-urlencoded"
  },
  body: 'method=getDocsMetaInfo%3a14%2e0%2e0%2e6009&#038;url%5flist=%5b' + encodeURIComponent(docUrl) + '%5d&#038;listHiddenDocs=false&#038;listLinkInfo=false',
}).then(function(source) {
  // go thru the source page returned to find the lockid and current user
  var nextLine = false;
  var ret = { "lockid":"", "user":"", when:"" };
  source.split("\n").forEach(function(line) {
    if (line.indexOf("vti_sourcecontrollockid") !== -1) nextLine="lockid"; // vti_sourcecontrollockid -> the lockid to use later
    else if (line.indexOf("vti_sourcecontrolcheckedoutby") !== -1) nextLine="user"; // vti_sourcecontrolcheckedoutby -> username of the user who locked it
    else if (line.indexOf("vti_sourcecontrollockexpires") !== -1) nextLine="when"; // vti_sourcecontrollockexpires -> when the server is supposed to unlock it
    else if (nextLine !== false) {
      ret[nextLine] = line.slice(7).replace(/&#([0-9]|[1-9][0-9]|[[01][0-9][0-9]|2[0-4][0-9]|25[0-5]);/g, function (str, match) { return  String.fromCharCode(match); });
      nextLine = false;
    }
  });

  if (!ret.lockid) { alert("Not Locked") }
  else {
    // compose a request based on what Microsoft Office sends to the Sharepoint server
    // found using Fiddler
    var releaseLockReq = '&lt;s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">&lt;s:Body>&lt;RequestVersion Version="2" MinorVersion="2" xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>&lt;RequestCollection CorrelationId="{96A244BD-D13B-4696-9355-231FB673BC4F}" xmlns="http://schemas.microsoft.com/sharepoint/soap/">&lt;Request Url="'+docUrl+'" UseResourceID="true" UserAgent="{1984108C-4B93-4EEB-B320-919432D6E593}" UserAgentClient="msword" UserAgentPlatform="win" Build="16.0.8201.2102" MetaData="1031" RequestToken="1">&lt;SubRequest Type="ExclusiveLock" SubRequestToken="1">&lt;SubRequestData ExclusiveLockRequestType="ReleaseLock" ExclusiveLockID="'+ret.lockid+'"/>&lt;/SubRequest>&lt;/Request>&lt;/RequestCollection>&lt;/s:Body>&lt;/s:Envelope>';

    // we send it to the webservice cellstorage.svc
    $SP().ajax({
      url:'https://website.com/website/_vti_bin/cellstorage.svc/CellStorageService',
      body:releaseLockReq,
      headers:{
        'Content-Type':'text/xml; charset=UTF-8',
        'SOAPAction': "http://schemas.microsoft.com/sharepoint/soap/ICellStorages/ExecuteCellStorageRequest"
      }
    })
    .then(function(res) {
      if (res.indexOf('ErrorCode="Success"') !== -1) alert("Success") // the file has been unlocked
      else alert("Failed")
    })
  }
})
</pre>
<p>I hope it will be useful to someone else!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2017/11/20/how-to-delete-a-document-locked-by-another-user-in-sharepoint-using-javascript/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Create an Unpublished Content view for the masterpage galery [Sharepoint]</title>
		<link>https://blog.kodono.info/wordpress/2017/05/18/create-an-unpublished-content-view-for-the-masterpage-galery-sharepoint/</link>
					<comments>https://blog.kodono.info/wordpress/2017/05/18/create-an-unpublished-content-view-for-the-masterpage-galery-sharepoint/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Thu, 18 May 2017 13:38:16 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[english]]></category>
		<category><![CDATA[sharepoint]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1792</guid>

					<description><![CDATA[With Sharepoint 2013 I wanted an easy way to list of the pages in my masterpage galery that haven&#8217;t been published yet. You first need to Create a new view named Unpublished Content. You can sort by Name, and scroll down to the Folders settings and choose Show all items without folders. We now have [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>With Sharepoint 2013 I wanted an easy way to list of the pages in my masterpage galery that haven&#8217;t been published yet.</p>
<p>You first need to <strong>Create a new view</strong> named <strong>Unpublished Content</strong>. You can <strong>sort by Name</strong>, and scroll down to the <strong>Folders settings</strong> and choose <strong>Show all items without folders</strong>.</p>
<p>We now have all our files in the view.</p>
<p>Next, switch to the <strong>Library ribbon</strong> and choose <strong>Modify in SharePoint Designer (Advanced)</strong> from the <strong>Modify View dropdown</strong>.</p>
<p>Sharepoint Designer will open your page.</p>
<p>In Sharepoint Designer, switch to the </strong>Home</strong> tab and choose <strong>Advanced Mode</strong>. The file is reloaded.</p>
<p>Search into your code the tag <strong>ListViewXml</strong>. On this line you should have <strong>&amp;lt;Query;&#038;&amp;gt;</strong>. Just after it, we need to add:<br />
<code>&amp;lt;Where&amp;gt;&amp;lt;Or&amp;gt;&amp;lt;Eq&amp;gt;&amp;lt;FieldRef Name="_ModerationStatus" /&amp;gt;&amp;lt;Value Type="ModStat"&amp;gt;Draft&amp;lt;/Value&amp;gt;&amp;lt;/Eq&amp;gt;&amp;lt;IsNotNull&amp;gt;&amp;lt;FieldRef Name="CheckoutUser" /&amp;gt;&amp;lt;/IsNotNull&amp;gt;&amp;lt;/Or&amp;gt;&amp;lt;/Where&amp;gt;</code></p>
<p>Before:<br />
<img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished1-1024x127.png" alt="" width="1024" height="127" class="aligncenter size-large wp-image-1793" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished1-1024x127.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished1-300x37.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished1-768x95.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished1.png 1121w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><br />
After:<br />
<a href="https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2.png" target="_blank"><img loading="lazy" decoding="async" src="https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2-1024x68.png" alt="" width="1024" height="68" class="aligncenter size-large wp-image-1794" srcset="https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2-1024x68.png 1024w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2-300x20.png 300w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2-768x51.png 768w, https://blog.kodono.info/wordpress/wp-content/uploads/2017/05/unpublished2.png 1714w" sizes="auto, (max-width: 1024px) 100vw, 1024px" />(click to enlarge)</a></p>
<p>Save, and your view should only show the unpublished content!</p>
<p>(Article inspired by <a href="https://thechriskent.com/2013/02/14/unpublished-view/">https://thechriskent.com/2013/02/14/unpublished-view/</a>)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2017/05/18/create-an-unpublished-content-view-for-the-masterpage-galery-sharepoint/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Capture HTTP(S) traffic from Android using a sniffer</title>
		<link>https://blog.kodono.info/wordpress/2016/11/07/capture-https-traffic-from-android-using-a-sniffer/</link>
					<comments>https://blog.kodono.info/wordpress/2016/11/07/capture-https-traffic-from-android-using-a-sniffer/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 07 Nov 2016 21:39:57 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[english]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1711</guid>

					<description><![CDATA[I wanted to debug an Android app that uses HTTPS requests with a JSON API. Thanks to this article I&#8217;ve been able to use my Windows 10 computer to get all the network from my Android phone thru my local network and decode the HTTPS requests ! I&#8217;m going to summarize the steps from the [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I wanted to debug an Android app that uses HTTPS requests with a JSON API. Thanks to <a href="http://www.cantoni.org/2013/11/06/capture-android-web-traffic-fiddler">this article</a> I&#8217;ve been able to use my Windows 10 computer to get all the network from my Android phone thru my local network and decode the HTTPS requests !</p>
<p>I&#8217;m going to summarize the steps from the above article:</p>
<ol>
<li>Download and Install <a href="http://fiddler2.com/">Fiddler</a> on your computer</li>
<li>Once Fiddler is installed, launch it and:
<ul>
<li>Click menu Tools > Options, then select the Connections tab</li>
<li>Make note of the “Fiddler listens on” port (normally it’s 8888)</li>
<li>Make sure the check box for “Allow remote computer to connect” is checked</li>
<li>Switch to the HTTPS tab</li>
<li>Make sure the check boxes for “Capture HTTPS Connects” and “Decrypt HTTPS traffic” are both checked</li>
<li>Restart Fiddler</li>
</ul>
</li>
<li>Go to your Android phone then:
<ul>
<li>Tap on Settings, then Wi-Fi</li>
<li>Find the network on which you’re connected (normally the first one listed), then tap and hold</li>
<li>Choose Modify network from the pop-up</li>
<li>Scroll down and enable “Show advanced options”</li>
<li>Change “Proxy settings” to Manual</li>
<li>Under “Proxy host name” enter the Windows PC IP address from above</li>
<li>Under “Proxy port” enter the Fiddler port from above (usually 8888)</li>
<li>Tap Save and wait a moment for the network to reconnect</li>
</ul>
</li>
<li>Now we need to add the certificate in Android to have the HTTPS working:
<ul>
<li>On Android start the Chrome browser</li>
<li>Navigate to http://IP_ADDRESS_WHERE_FIDDLER_IS:8888/ or <a href="http://ipv4.fiddler:8888">http://ipv4.fiddler:8888</a></li>
<li>Tap on the link for the “Fiddler Root Certificate”</li>
<li>Name the certificate “Fiddler” and install it (entering your PIN or password if prompted)</li>
</ul>
</li>
</ol>
<p>You&#8217;re now ready to capture the traffic on Fiddler!</p>
<p>Once you&#8217;re done you can switch back to normal by following the below steps:</p>
<ol>
<li>Tap on Settings, then Wi-Fi</li>
<li>Find the network on which you’re connected (should be the first one listed), then tap and hold</li>
<li>Choose Modify network from the pop-up</li>
<li>Scroll down and select (enable) “Show advanced options”</li>
<li>Change “Proxy settings” to None</li>
<li>Tap Save and wait a moment for the network to reconnect</li>
<li>Go up a level in settings to Security</li>
<li>Tap Trusted credentials, then select the User tab</li>
<li>Tap on the Fiddler “Do not trust” certificate, then scroll down to remove it</li>
<li>You may need to power cycle your device to get all apps to forget about the Fiddler certificate (e.g., the Chrome browser will continue to try to use it for a while)</li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2016/11/07/capture-https-traffic-from-android-using-a-sniffer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Mise à jour d&#8217;un serveur Kimsufi (OVH) depuis Debian 7.10 (Wheezy) vers Debian 8 (Jessie) [Linux]</title>
		<link>https://blog.kodono.info/wordpress/2016/04/13/mise-a-jour-dun-serveur-kimsufi-ovh-depuis-debian-7-10-wheezy-vers-debian-8-jessie-linux/</link>
					<comments>https://blog.kodono.info/wordpress/2016/04/13/mise-a-jour-dun-serveur-kimsufi-ovh-depuis-debian-7-10-wheezy-vers-debian-8-jessie-linux/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Wed, 13 Apr 2016 09:14:31 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<guid isPermaLink="false">https://blog.kodono.info/wordpress/?p=1618</guid>

					<description><![CDATA[Il faut régulièrement penser à mettre à jour son serveur Kimsufi. Je vais essayer d&#8217;expliquer brièvement les étapes à suivre pour cela. On va d&#8217;abord sauvegarder les données : mkdir /root/svg_special; cp -R /var/lib/dpkg /root/svg_special/; cp /var/lib/apt/extended_states /root/svg_special/; dpkg --get-selections "*" > /root/svg_special/dpkg_get_selection; cp -R /etc /root/svg_special/etc Ensuite il est conseillé d&#8217;utiliser screen pour pouvoir [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Il faut régulièrement penser à mettre à jour son serveur Kimsufi.</p>
<p>Je vais essayer d&#8217;expliquer brièvement <a href="https://www.debian.org/releases/stable/i386/release-notes/ch-upgrading.fr.html">les étapes à suivre</a> pour cela.</p>
<ol>
<li>On va d&#8217;abord sauvegarder les données :
<pre class="brush:powershell">mkdir /root/svg_special; cp -R /var/lib/dpkg /root/svg_special/; cp /var/lib/apt/extended_states /root/svg_special/; dpkg --get-selections "*" > /root/svg_special/dpkg_get_selection; cp -R /etc /root/svg_special/etc</pre>
</li>
<li>Ensuite il est conseillé d&#8217;utiliser <code>screen</code> pour pouvoir se reconnecter (avec <code>screen -r</code>) à en cas de déconnexion :
<pre class="brush:powershell">screen</pre>
</li>
</li>
<li>On peut lancer la commande <code>dpkg --audit</code> pour s&#8217;assurer que tout est bon avant la migration. On peut également taper <code>dpkg --get-selections "*" | more</code> et vérifier qu&#8217;aucun paquet n&#8217;est en <em>on hold</em></li>
<li>Maintenant il faut remplacer tous les &#8220;wheezy&#8221; de <code>/etc/apt/sources.list</code> par des &#8220;jessie&#8221;, ce qui va donner chez moi :
<pre class="brush:bash">
deb http://ftp.fr.debian.org/debian jessie main non-free

deb http://debian.mirrors.ovh.net/debian/ jessie main
deb-src http://debian.mirrors.ovh.net/debian/ jessie main

deb http://security.debian.org/ jessie/updates main
deb-src http://security.debian.org/ jessie/updates main
</pre>
</li>
<li>Il est recommandé d&#8217;utiliser le programme <code>/usr/bin/script</code> pour enregistrer une transcription de la session de mise à niveau. Ainsi, quand un problème survient, on a un enregistrement de ce qui s&#8217;est passé. Pour démarrer un enregistrement, taper :
<pre class="brush:powershell">script -t 2>~/upgrade-jessie.time -a ~/upgrade-jessie.script</pre>
</li>
<li>On passe aux choses sérieuses, en commençant par mettre à jour les listes des paquets :
<pre class="brush:powershell">apt-get update</pre>
</li>
<li>On va vérifier qu&#8217;on a la place suffisante (un message explicite apparait sinon) :
<pre class="brush:powershell">apt-get -o APT::Get::Trivial-Only=true dist-upgrade</pre>
</li>
<li>On va maintenant faire une mise à jour minimale :
<pre class="brush:powershell">apt-get upgrade</pre>
</li>
<li>Et à partir de là le système va vous questionner&#8230; en général choisir l&#8217;option par défaut si vous ne savez pas quoi répondre</li>
<li>Puis on continue avec
<pre class="brush:powershel">apt-get dist-upgrade</pre>
</li>
</ol>
<p>Cette dernière étape va durer un certain temps. Une fois terminé, vous pouvez redémarrer le serveur pour s&#8217;assurer que tout va bien.</p>
<p>Après tout ça j&#8217;ai rencontré un problème avec la version 2.4 d&#8217;Apache, en ayant l&#8217;erreur :</p>
<blockquote><p>AH01630: client denied by server configuration</p></blockquote>
<p>En cherchant j&#8217;ai trouvé <a href="http://httpd.apache.org/docs/2.4/upgrading.html#access">des modifications au niveau de la configuration</a>, à savoir qu&#8217;il faut mettre <strong>Require all granted</strong> dans tous les <strong>&lt;Directory&gt;</strong> des virtual hosts :</p>
<pre class="brush:sh">
&lt;Directory />
  Options FollowSymLinks
  Require all granted
&lt;/Directory>
</pre>
<p>De même, concernant <strong>phpmyadmin</strong>, il faut modifier <code>/etc/phpmyadmin/apache.conf</code> (avec <strong>Require all denied</strong> par exemple).</p>
<p>Avec <strong>phpmyadmin</strong> vous pourriez recevoir l&#8217;erreur suivante :</p>
<blockquote><p>PHP Fatal error: require_once(): Failed opening required &#8216;./libraries/php-gettext/gettext.inc&#8217; (include_path=&#8217;.&#8217;) in /usr/share/phpmyadmin/libraries/select_lang.lib.php</p></blockquote>
<p>Dans ce cas là, il faut rajouter <code>/usr/share/php/php-gettext/</code> dans le fichier <code>/etc/phpmyadmin/apache.conf</code> sur la ligne <code>open_base_dir</code> (<a href="http://superuser.com/questions/590208/phpmyadmin-symlinks-error-after-ubuntu-upgrade">voir superuser.com</a>). Ce qui va donner la ligne : <code>php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/php-gettext/</code></p>
<p>Mon fichier <code>apache2.conf</code> avait été aussi modifié et certains fichiers de configuration n&#8217;étaient plus lus (ceux dans le répertoire <code>/etc/apache2/conf.d/</code>). De même il faudra renommer les fichiers présents dans <code>/etc/apache2/sites-available/</code> en y ajoutant l&#8217;extension <strong>.conf</strong> (et relancer la commande <code>a2ensite</code> sur vos fichiers renommés).</p>
<p>Si vous utilisez des fichiers <code>.htaccess</code> (par exemple avec WordPress), alors assurez vous d&#8217;utiliser la balise <code>AllowOverride All</code> &#8230; par exemple dans un de vos fichiers de configuration d&#8217;Apache il faudra mettre (pour indiquer que tous les <code>.htaccess</code> dans <code>/home/websites/www</Code> sont autorisés) :</p>
<pre class="brush:pw">
&lt;Directory /home/websites/www>
   AllowOverride All
&lt;/Directory>
</pre>
<p>Sur un des serveurs il y avait un problème d'encodage avec les fichiers en PHP alors que les HTML n'avaient pas de soucis. Après avoir cherché j'ai découvert que dans le cas de ce serveur je devais modifier <code>/etc/php5/apache2/php.ini</code> pour y mettre <code>default_charset = Off</code>.</p>
<p>Et finalement on va nettoyer tous les paquets avec </p>
<pre class="brush:powershell">apt-get autoremove</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2016/04/13/mise-a-jour-dun-serveur-kimsufi-ovh-depuis-debian-7-10-wheezy-vers-debian-8-jessie-linux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Passer son serveur Apache en HTTPS avec Let&#8217;s Encrypt</title>
		<link>https://blog.kodono.info/wordpress/2016/04/06/passer-son-serveur-apache-en-https-avec-lets-encrypt/</link>
					<comments>https://blog.kodono.info/wordpress/2016/04/06/passer-son-serveur-apache-en-https-avec-lets-encrypt/#comments</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Wed, 06 Apr 2016 20:51:33 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Sécurité]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1608</guid>

					<description><![CDATA[Je vais expliquer les différentes étapes pour passer un serveur Apache en HTTPS grâce à Let&#8217;s Encrypt. Toutes les opérations vont se passer dans une console sur le serveur Linux en mode root. J&#8217;utilise ici une Debian 7.10 « Wheezy ». Je me suis aidé de cet article en anglais. Commencer par nettoyer le contenu [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Je vais expliquer les différentes étapes pour passer un serveur Apache en HTTPS grâce à <a href="https://letsencrypt.org/">Let&#8217;s Encrypt</a>.</p>
<p>Toutes les opérations vont se passer dans une console sur le serveur Linux en mode root. J&#8217;utilise ici une Debian 7.10 « Wheezy ».</p>
<p>Je me suis aidé de <a href="https://thealphanerd.io/blog/securing-apache-and-znc-with-letsencrypt/">cet article en anglais</a>.</p>
<ol>
<li>Commencer par nettoyer le contenu de <code>/etc/apache2/sites-available/</code> en supprimant tous les fichiers inutiles</li>
<li>Aller dans <code>/root</code></li>
<li>Installer <strong>git</strong> (s&#8217;il n&#8217;est pas déjà installer) : <code>$ apt-get install git</code></li>
<li>Ensuite on tape les commandes suivantes :
<ul>
<li><code>$ git clone https://github.com/letsencrypt/letsencrypt</code></li>
<li><code>$ cd letsencrypt</code></li>
<li><code>$ ./letsencrypt-auto --help</code></li>
</ul>
</li>
<li>Plusieurs programmes vont s&#8217;installer.</li>
<li>Maintenant on tape : <code>$ ./letsencrypt-auto --apache</code></li>
<li>Si vous obtenez l&#8217;erreur <em>Apache plugin support requires libaugeas0 and augeas-lenses version 1.2.0 or higher, please make sure you have you have those installed.</em> alors il va falloir utiliser un <a href="http://backports.debian.org/">backport debian repositories</a> :
<ul>
<li>On commence par éditer le fichier <code>/etc/apt/sources.list</code> en y ajoutant la ligne <code>deb http://ftp.debian.org/debian wheezy-backports main</code></li>
<li>Puis on tape <code>$ apt-get update</code></li>
<li>Et ensuite <code>$ apt-get install -t wheezy-backports libaugeas0 augeas-lenses</code></li>
<li>On peut maintenant refaire <code>$ ./letsencrypt-auto --verbose --apache</code></li>
</ul>
<li>Une boite de dialogue s&#8217;ouvre vous indiquant les domaines trouvés sur votre machine. Par défaut ils sont tous cochés. Suivez les instructions</li>
<li>Une fois fait, si vous avez l&#8217;erreur <em>urn:acme:error:connection :: The server could not connect to the client to verify the domain :: Failed to connect to host for DVSNI challenge</em> il peut y avoir plusieurs raisons&#8230; Pour ma part c&#8217;était le firewall qui bloquait le port 443</li>
<li>Ou si vous avez une erreur liée à un domaine, assurez-vous que celui-ci pointe bien vers votre box en utilisant la commande <code>nslookup -debug blog.kodono.info 8.8.8.8</code></li>
</ol>
<p>Vous devriez maintenant avoir accès à votre site web avec <strong>https</strong>.</p>
<p><strong>Attention</strong> car le certificat Let&#8217;s Encrypt expire après 90 jours. Il va donc falloir mettre en place un <strong>cron job</strong>. On peut prendre celui de <a href="https://thealphanerd.io/blog/securing-apache-and-znc-with-letsencrypt/">https://thealphanerd.io/blog/securing-apache-and-znc-with-letsencrypt/</a> :</p>
<pre class="brush:bash">
#!/bin/sh
if ! /path/to/letsencrypt/letsencrypt-auto renew > /var/log/letsencrypt/renew.log 2>&#038;1 ; then
  echo Automated renewal failed:
  cat /var/log/letsencrypt/renew.log
  exit 1
fi
</pre>
<p>On va le placer dans <code>/etc/cron.daily/update-certs</code> (avec les droits <strong>755</strong>), et le cron va s&#8217;en occuper tout seul.</p>
<p>Si vous continuez à autoriser le HTTP et le HTTPS, alors, pour éviter le <em>mixed content</em> (c&#8217;est-à-dire du contenu http qui est appelé sur votre site https) on peut se la jouer brut-force en utilisant un module d&#8217;Apache qui va modifier le contenu des pages avant de les renvoyer. À mon sens cela devrait être temporaire, le temps de modifier tous vos fichiers.</p>
<ol>
<li>On va commencer par installer le module substitude: <code>a2enmod substitute</code></li>
<li>Ensuite on utilise le nouveau module pour remplacer les <code>http://notresite</code> en <code>https://notresite</code>, ainsi que les liens extérieurs de <code>http://</code> en <code>//</code>. Pour cela on modifie nos fichiers .conf dans <code>/etc/apache2/sites-available/</code> en y ajoutant :</li>
</ol>
<pre class="brush:bash"> &lt;Location />
 &lt;If "%{SERVER_PORT} == 443">
 AddOutputFilterByType INFLATE;SUBSTITUTE;DEFLATE text/html text/plain text/xml
 Substitute "s|action=\"http:|action=\"|"
 Substitute "s|action='http:|action='|"
 Substitute "s|src=\"http:|src=\"|"
 Substitute "s|src='http:|src='|"
 Substitute "s|href=\"http:|href=\"|"
 Substitute "s|href='http:|href='|"
 &lt;If>
&lt;/Location>
</pre>
<p>J&#8217;utilise <code>&lt;If "%{SERVER_PORT} == 443"></code> pour n&#8217;enclencher la substitution que lorsqu&#8217;on demande de l&#8217;HTTPS.<br />
Et on oubliera pas de faire un <code>apache2ctl configtest</code> pour vérifier que tout est bon, puis un <code>service apache2 reload</code> pour prendre en compte les modifications !</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2016/04/06/passer-son-serveur-apache-en-https-avec-lets-encrypt/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Mise à jour mysql 5.5 vers 5.6, sur Debian Wheezy</title>
		<link>https://blog.kodono.info/wordpress/2016/01/16/mise-a-jour-mysql-5-5-vers-5-6-sur-debian-wheezy/</link>
					<comments>https://blog.kodono.info/wordpress/2016/01/16/mise-a-jour-mysql-5-5-vers-5-6-sur-debian-wheezy/#comments</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sat, 16 Jan 2016 18:58:42 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[debian]]></category>
		<category><![CDATA[mysql]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1598</guid>

					<description><![CDATA[1) Nouveau repo On crée d&#8217;abord le fichier /etc/apt/sources.list.d/mysql.list avec les deux lignes ci-dessous : deb http://repo.mysql.com/apt/debian/ wheezy mysql-5.6 deb-src http://repo.mysql.com/apt/debian/ wheezy mysql-5.6 2) Clé publique du repo On crée un fichier mysql.key dans lequel on copie/colle la clé publique de mysql. Puis on l&#8217;ajoute à apt : apt-key add mysql.key 3) Export / Backup [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><strong>1) Nouveau repo</strong></p>
<p>On crée d&#8217;abord le fichier <code>/etc/apt/sources.list.d/mysql.list</code> avec les deux lignes ci-dessous :<br />
<code>deb http://repo.mysql.com/apt/debian/ wheezy mysql-5.6<br />
deb-src http://repo.mysql.com/apt/debian/ wheezy mysql-5.6</code></p>
<p><strong>2) Clé publique du repo</strong></p>
<p>On crée un fichier <code>mysql.key</code> dans lequel on copie/colle la <a href="http://dev.mysql.com/doc/refman/5.7/en/checking-gpg-signature.html">clé publique de mysql</a>.</p>
<p>Puis on l&#8217;ajoute à apt :<br />
<code>apt-key add mysql.key</code></p>
<p><strong>3) Export / Backup</strong></p>
<p>On va faire une sauvegarde de la base de données et de la configuration de mysql :<br />
<code>mysqldump -u root -pPASSWORD --add-drop-table --routines --events --add-drop-table --all-databases --force > data-for-upgrade.sql<br />
tar cvfvz /root/mysql_conf.tgz /etc/mysql </code></p>
<p><strong>4) Arrêt du serveur</strong></p>
<p>On stoppe mysql :<br />
<code>mysqladmin -u root -pPASSWORD shutdown</code></p>
<p><strong>5) On met à jour apt</strong></p>
<p><code>apt-get update</code></p>
<p><strong>6) On installe</strong></p>
<p>On installe la nouvelle version :<br />
<code>apt-get install mysql-server-5.6</code></p>
<p><strong>7) On vérifie</strong></p>
<p>Une fois fait, on lance :<br />
<code>mysql_upgrade -v -u root -pPASSWORD</code></p>
<p><strong>8) Dernier upgrade</strong></p>
<p>On lance l&#8217;upgrade pour que tout soit à jour :<br />
<code>apt-get upgrade</code></p>
<p><strong>9) Mise à jour de PHP-Mysql</strong></p>
<p>Si vous avez l&#8217;erreur <a href="https://www.skyminds.net/mysql-resoudre-lerreur-mysql_connect-headers-and-client-library-minor-version-mismatch/">mysql_connect(): Headers and client library minor version mismatch</a>, il est recommandé d&#8217;installer <code>php5-mysqlnd</code> :<br />
<code>apt-get install php5-mysqlnd</code></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2016/01/16/mise-a-jour-mysql-5-5-vers-5-6-sur-debian-wheezy/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>VirtualBox qui utilise le VPN de la machine hôte</title>
		<link>https://blog.kodono.info/wordpress/2015/12/30/virtualbox-qui-utilise-le-vpn-de-la-machine-hote/</link>
					<comments>https://blog.kodono.info/wordpress/2015/12/30/virtualbox-qui-utilise-le-vpn-de-la-machine-hote/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Wed, 30 Dec 2015 14:53:13 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[virtualbox]]></category>
		<category><![CDATA[VPN]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1587</guid>

					<description><![CDATA[Dans le cadre de mon travail j&#8217;ai dû utiliser VirtualBox afin de pouvoir tester différentes versions d&#8217;IE. Mon problème est que mon PC utilise normalement un VPN pour accéder au réseau de l&#8217;entreprise et que ma VM n&#8217;arrive pas à y accéder de base&#8230;. Pour y remédier il faut d&#8217;abord définir deux adaptateurs dans l&#8217;onglet [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Dans le cadre de mon travail j&#8217;ai dû utiliser VirtualBox afin de pouvoir tester différentes versions d&#8217;IE. Mon problème est que mon PC utilise normalement un VPN pour accéder au réseau de l&#8217;entreprise et que ma VM n&#8217;arrive pas à y accéder de base&#8230;.</p>
<p>Pour y remédier il faut d&#8217;abord définir deux adaptateurs dans l&#8217;onglet Network de la VM :</p>
<ol>
<li>NAT (s&#8217;assurer que &#8220;Cable Connected&#8221; est coché)</li>
<li>Bridged Adapter (avec &#8220;Promiscous Mode&#8221;: Allow All, et &#8220;Cable Connected&#8221; de coché)</li>
</ol>
<p>Ensuite, au niveau de la machine virtuelle, il devrait y avoir deux connexions :</p>
<ol>
<li>Une qui doit avoir une IP en 10.x.x.x (réseau du VPN)</li>
<li>La deuxième qui doit avoir une IP de notre réseau local (192.168.0.x chez moi) &#8230; IP Local à définir manuellement si nécessaire, et à noter qu&#8217;il faut aussi définir les DNS manuellement (j&#8217;utilise ceux de Google) si on veut que ça fonctionne bien</li>
</ol>
<p>A partir d&#8217;ici votre système devrait être en mesure d&#8217;accéder au réseau VPN.</p>
<p><strong>(ci-dessous une configuration que je dois faire pour réussir à avoir un semblant d&#8217;accès au reste du Net &#8230; à noter que ça rend les choses instables) </strong></p>
<p>Dans l&#8217;exemple qui suit on va supposer que ma gateway pour le VPN est 10.0.2.2 et pour le réseau local 192.168.0.254</p>
<p>Ensuite j&#8217;ai modifié les routes en ouvrant une console <code>cmd</code> en tant qu&#8217;administrateur puis je tape :<br />
<code>route print</code> (pour trouver le numéro des interfaces, on va dire que #13 est pour 10.x.x.x et #15 pour l&#8217;interface en 192.168.0.x)<br />
<code>route -f</code> (on flush les règles existantes)<br />
<code>route add 0.0.0.0 mask 0.0.0.0 192.168.0.254 IF 15</code> (par défaut tout le trafic passe par le réseau normal)<br />
<code>route add 10.0.0.0 mask 255.0.0.0 10.0.2.2 IF 13</code> (et tout ce qui concerne le réseau du VPN on l&#8217;envoie vers celui-ci)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/12/30/virtualbox-qui-utilise-le-vpn-de-la-machine-hote/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Android 5.1.1 system rw Operation not permitted</title>
		<link>https://blog.kodono.info/wordpress/2015/07/26/android-5-1-1-system-rw-operation-not-permitted/</link>
					<comments>https://blog.kodono.info/wordpress/2015/07/26/android-5-1-1-system-rw-operation-not-permitted/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Sun, 26 Jul 2015 21:52:10 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[android]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1525</guid>

					<description><![CDATA[I was trying to update my Sony Xperia Z3 Compact from Android 5.0.2 (rooted) to Android 5.1.1, and everything worked well except that I got this issue : mount: Operation not permitted when I was trying to put in write mode the system folder. Also I tried SDFix and I got the error &#8220;Update failed, [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I was trying to update my Sony Xperia Z3 Compact from Android 5.0.2 (rooted) to Android 5.1.1, and everything worked well except that I got this issue : <code>mount: Operation not permitted<br />
</code> when I was trying to put in write mode the system folder.</p>
<p>Also I tried SDFix and I got the error &#8220;Update failed, platform.xml file could not be updated&#8221;.</p>
<p>When I tried &#8220;ES Explorer&#8221;, with Root Mode, I had SuperSU asking for root permission, but then &#8220;ES Explorer&#8221; said my system is not rooted.</p>
<p>After hours of digging I finally found I had to use <a href="https://mega.co.nz/#!CJMzVTJD!INyqTPX601_cFJbpNHM9iNoOTu8NC1_3I8Pqq9OHrs0">that file</a> discovered in that <a href="http://forum.xda-developers.com/z3-compact/development/d5803-lollipop-5-02-odexed-pre-rooted-2-t3090505">XDA thread</a>. Just download it, unzip, then launch the <code>install.bat</code to see the magic !
</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/07/26/android-5-1-1-system-rw-operation-not-permitted/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Root/directory, 0x8004100E &#8211; (WBEM_E_INVALID_NAMESPACE) Namespace specified cannot be found</title>
		<link>https://blog.kodono.info/wordpress/2015/05/18/rootdirectory-0x8004100e-wbem_e_invalid_namespace-namespace-specified-cannot-be-found/</link>
					<comments>https://blog.kodono.info/wordpress/2015/05/18/rootdirectory-0x8004100e-wbem_e_invalid_namespace-namespace-specified-cannot-be-found/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 18 May 2015 19:09:23 +0000</pubDate>
				<category><![CDATA[Debug]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1508</guid>

					<description><![CDATA[I think I found the MOF file related to the above error. To fix it you might try: C:\Windows\System32\wbem>mofcomp DscCore.mof]]></description>
										<content:encoded><![CDATA[<p>I think I found the MOF file related to the above error. To fix it you might try:</p>
<pre>C:\Windows\System32\wbem>mofcomp DscCore.mof</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/05/18/rootdirectory-0x8004100e-wbem_e_invalid_namespace-namespace-specified-cannot-be-found/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>standardCimv2 or MSFT_NetIPAddress missing from WMI</title>
		<link>https://blog.kodono.info/wordpress/2015/05/18/standardcimv2-or-msft_netipaddress-missing-from-wmi/</link>
					<comments>https://blog.kodono.info/wordpress/2015/05/18/standardcimv2-or-msft_netipaddress-missing-from-wmi/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 18 May 2015 18:33:42 +0000</pubDate>
				<category><![CDATA[Debug]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1506</guid>

					<description><![CDATA[I spent some time trying to figure out why root\standardCimv2 was missing on my Windows 8.1. Finally it was an issue with a MOF file in WMI. To fix it: C:\Windows\System32\wbem>mofcomp NetAdapterCim.mof]]></description>
										<content:encoded><![CDATA[<p>I spent some time trying to figure out why <code>root\standardCimv2</code> was missing on my Windows 8.1.</p>
<p>Finally it was an issue with a MOF file in WMI. To fix it:</p>
<pre>C:\Windows\System32\wbem>mofcomp NetAdapterCim.mof</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/05/18/standardcimv2-or-msft_netipaddress-missing-from-wmi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Win32_Processor : invalide classe</title>
		<link>https://blog.kodono.info/wordpress/2015/05/18/win32_processor-invalide-classe/</link>
					<comments>https://blog.kodono.info/wordpress/2015/05/18/win32_processor-invalide-classe/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 18 May 2015 16:07:09 +0000</pubDate>
				<category><![CDATA[Debug]]></category>
		<category><![CDATA[Divers]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1503</guid>

					<description><![CDATA[J&#8217;ai eu un certain nombre d&#8217;erreurs qui m&#8217;ont amené à chercher comment fixer wmi concernant le message : « Win32_Processor : invalide classe » J&#8217;ai finalement trouvé comment faire grâce à https://katyscode.wordpress.com/2007/02/03/tutorial-how-to-fix-wmi-corruption/ &#8212; il suffit de faire : C:\Windows\System32\wbem>mofcomp cimwin32.mof Compilateur MOF Microsoft (R) - Version 6.3.9600.16384 Copyright (c) Microsoft Corp. 1997-2006. Tous droits réservés. [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>J&#8217;ai eu un certain nombre d&#8217;erreurs qui m&#8217;ont amené à chercher comment fixer wmi concernant le message : « Win32_Processor : invalide classe »</p>
<p>J&#8217;ai finalement trouvé comment faire grâce à <a href="https://katyscode.wordpress.com/2007/02/03/tutorial-how-to-fix-wmi-corruption/">https://katyscode.wordpress.com/2007/02/03/tutorial-how-to-fix-wmi-corruption/</a> &#8212; il suffit de faire :</p>
<pre>C:\Windows\System32\wbem>mofcomp cimwin32.mof
Compilateur MOF Microsoft (R) - Version 6.3.9600.16384
Copyright (c) Microsoft Corp. 1997-2006. Tous droits réservés.
Analyse du fichier MOF : cimwin32.mof
Analyse du fichier MOF effectuée
Stockage des données...
Terminé !</pre>
<p>Et un autre lien utile : <a href="http://blogs.technet.com/b/askperf/archive/2009/04/13/wmi-rebuilding-the-wmi-repository.aspx">http://blogs.technet.com/b/askperf/archive/2009/04/13/wmi-rebuilding-the-wmi-repository.aspx</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/05/18/win32_processor-invalide-classe/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Ajouter &#8220;nocaptcha reCaptcha&#8221; de Google à Guiform sous WordPress [Astuce]</title>
		<link>https://blog.kodono.info/wordpress/2015/03/09/ajouter-nocaptcha-recaptcha-de-google-a-guiform-sous-wordpress-astuce/</link>
					<comments>https://blog.kodono.info/wordpress/2015/03/09/ajouter-nocaptcha-recaptcha-de-google-a-guiform-sous-wordpress-astuce/#respond</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Mon, 09 Mar 2015 16:57:15 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1463</guid>

					<description><![CDATA[J&#8217;utilise GuiForm pour faire des formulaires dans un WordPress, et je voulais y ajouter le captcha de Google sans pour autant devoir payer la licence chère de GuiForm juste pour ça&#8230; Pour réussir ce que je décris ci-dessous, il vous faudra connaitre votre clé privée et publique de Google Captcha. Il faut commencer par faire [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>J&#8217;utilise <a href="https://wordpress.org/plugins/guiform/">GuiForm</a> pour faire des formulaires dans un WordPress, et je voulais y ajouter le captcha de Google sans pour autant devoir payer la licence chère de GuiForm juste pour ça&#8230;</p>
<p>Pour réussir ce que je décris ci-dessous, il vous faudra connaitre <a href="https://www.google.com/recaptcha/admin">votre clé privée et publique de Google Captcha</a>.</p>
<p>Il faut commencer par faire son formulaire (de contact dans mon cas) avec tous les champs voulus via GuiForm, puis on ajoute un champ de type &#8220;Heading&#8221; avec comme contenu <strong>&#8220;captcha&#8221;</strong>. On sauvegarde le formulaire.</p>
<p>Maintenant dans le fichier <code>functions.php</code> de votre thème il faut rajouter :</p>
<pre class="brush:php">
// on veut rajouter un captcha dans le formulaire de contact
// pour ça on surcharge 'guiform_render_form' qui est appelé par le plugin
function addCaptchaToGuiForm($content) {
  // on va remplacer "&lt;h1>captcha&lt;/h1>" par ce qu'il faut
  $content = str_replace("&lt;h1>captcha&lt;/h1>", "&lt;div class='g-recaptcha' style='float:right' data-sitekey='VOTRE_CLE_PUBLIQUE' data-theme='light'>&lt;/div>&lt;script src='https://www.google.com/recaptcha/api.js'>&lt;/script>",$content);
  // on rajoute aussi du JavaScript
  $content .= '&lt;script>'."\r\n".
              "function waitForjQuery() {"."\r\n".
              "  if (typeof jQuery === 'undefined') { setTimeout(waitForjQuery, 50); return }"."\r\n".
              "  var submit = jQuery('.f_submit');"."\r\n".
              "  var html = '&lt;div class=\"'+submit[0].className+'\">'+submit.html()+'&lt;/div>';"."\r\n".
              "  submit.before(html).find('button').hide().prop('disabled', true);"."\r\n".
              "  var cloneSubmit = jQuery('.f_submit:first');"."\r\n".
              "  cloneSubmit.on('click', function(event) {"."\r\n".
              "    event.preventDefault();"."\r\n".
              "    jQuery.ajax({"."\r\n".
              "      type: 'GET',"."\r\n".
              "      url: '/wp-content/themes/VOTRE_THEME/checkCaptcha?response='+grecaptcha.getResponse(),"."\r\n".
              "      success: function(data) {"."\r\n".
              "        if (data['success'] === true) {"."\r\n".
              "          jQuery('.f_submit:first').remove()"."\r\n".
              "          jQuery('.f_submit').find('button').prop('disabled',false).show().last().trigger('click');"."\r\n".
              "        }"."\r\n".
              "        else if (data['error-codes']) {"."\r\n".
              "          switch (data['error-codes'][0]) {"."\r\n".
              "            case 'missing-input-secret': "."\r\n".
              "            case 'invalid-input-secret': alert('Erreur : impossible de vérifier le système anti-spam.'); break;"."\r\n".
              "            case 'missing-input-response': "."\r\n".
              "            case 'invalid-input-response': "."\r\n".
              "            default: alert('Erreur : vous devez répondre à l\'anti-spam.');"."\r\n".
              "          }"."\r\n".
              "        }"."\r\n".
              "      },"."\r\n".
              "      dataType: 'json'"."\r\n".
              "    });"."\r\n".
              "  })"."\r\n".
              "}"."\r\n".
              "waitForjQuery();"."\r\n".
              "&lt;/script>";
  return $content;
}
add_action('guiform_render_form', 'addCaptchaToGuiForm');
</pre>
<p>Grâce à cette astuce on modifie notre <code>&lt;h1>captcha&lt;/h1></code> par le code de Google. On ajoute un soupçon de JavaScript qui va permettre de cacher le vrai bouton &#8220;Submit&#8221;. Lorsqu&#8217;on va cliquer sur le faux bouton, le catpcha va être vérifié. Une fois fait le formulaire classique peut-être utilisé.</p>
<p><strong>Attention</strong> : dans le code ci-dessus il faut remplacer <em>VOTRE_CLE_PUBLIQUE</em> et <em>VOTRE_THEME</em> par les valeurs correspondantes.</p>
<p>Enfin on a besoin de créer un fichier PHP à la racine de notre thème, qui va s&#8217;appeler <code>checkCaptcha.php</code> et qui aura comme contenu :</p>
<pre class="brush:php">
&lt;?php
$url = "https://www.google.com/recaptcha/api/siteverify?secret=VOTRE_CLE_PRIVEE&#038;response=".$_GET["response"];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
$output = curl_exec($curl);
curl_close($curl);
echo $output;
?>
</pre>
<p><strong>Attention</strong> : dans le code ci-dessus il faut remplacer <em>VOTRE_CLE_PRIVEE</em> par la valeur correspondante.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/03/09/ajouter-nocaptcha-recaptcha-de-google-a-guiform-sous-wordpress-astuce/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Activer la compression gzip / deflate sur 1and1 [Astuce]</title>
		<link>https://blog.kodono.info/wordpress/2015/03/06/activer-la-compression-gzip-deflate-sur-1and1-astuce/</link>
					<comments>https://blog.kodono.info/wordpress/2015/03/06/activer-la-compression-gzip-deflate-sur-1and1-astuce/#comments</comments>
		
		<dc:creator><![CDATA[Aymeric]]></dc:creator>
		<pubDate>Fri, 06 Mar 2015 14:43:00 +0000</pubDate>
				<category><![CDATA[Astuce]]></category>
		<category><![CDATA[Niveau expert]]></category>
		<guid isPermaLink="false">http://blog.kodono.info/wordpress/?p=1453</guid>

					<description><![CDATA[Ayant un WordPress chez 1and1 j&#8217;ai voulu activer la compression de mes pages. Après avoir longuement cherché de partout, j&#8217;ai trouvé comment procéder, et ce n&#8217;est vraiment pas simple&#8230;. En effet, il va falloir faire passer tous nos fichiers (js, css, html, &#8230;) par PHP afin qu&#8217;ils soient compressés à la volée. Chemin d&#8217;accès complet [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Ayant un WordPress chez 1and1 j&#8217;ai voulu activer la compression de mes pages. Après avoir longuement cherché de partout, j&#8217;ai trouvé comment procéder, et ce n&#8217;est vraiment pas simple&#8230;. En effet, il va falloir faire passer tous nos fichiers (js, css, html, &#8230;) par PHP afin qu&#8217;ils soient compressés à la volée.</p>
<h2>Chemin d&#8217;accès complet</h2>
<p>Il nous faut le chemin d&#8217;accès complet chez 1and1. Pour cela on va mettre un fichier temporaire à la racine de notre site qui se nomme <code>info.php</code> dans lequel on inscrit :</p>
<pre class="brush:php">
&lt;?php
phpinfo();
?>
</pre>
<p>On accède à cette page via un navigateur pour avoir toutes les informations liées à PHP. On y cherche la ligne qui correspond à <code>DOCUMENT_ROOT</code> afin de trouver le chemin d&#8217;accès complet de notre site ; par exemple <code>/kunden/homepages/25/d3506178849/htdocs/clickandbuilds/WordPress/</code>.</p>
<p><strong>Supprimer le fichier info.php</strong>.</p>
<h2>Création du prepend file</h2>
<p>Maintenant on crée un nouveau fichier à la racine de notre site qui se nomme <strong>headers.php</strong> dans lequel on met :</p>
<pre class="brush:php">
&lt;?php
$pathinfo=pathinfo($_ENV['SCRIPT_FILENAME']); 
$extension=$pathinfo['extension']; 
switch ($extension) {
  case 'css':
    $contentType = 'text/css';
    break;
  case 'js':
    $contentType = 'application/x-javascript';
    break;
  case 'xml':
    $contentType = 'text/xml';
    break;
  default:
    $contentType = 'text/html';
    break;
}
header("Content-type: ".$contentType);
// pour vérifier que notre script est bien exécuté
header("X-Homemade-Compression: OK");
?>
</pre>
<p>Ce fichier va être appelé pour chaque page et va permettre d&#8217;envoyer le bon <code>Content-Type</code>.</p>
<h2>Fichiers <em>.htaccess</em> et <em>php.ini</em></h2>
<p>Maintenant, cela se complique car il va falloir repérer où se situent toutes les ressources que l&#8217;on souhaite compresser. Dans le cas de WordPress, il devrait y avoir :</p>
<ul>
<li>/wp-includes/js/jquery/*</li>
<li>/wp-content/themes/<em>votretheme</em>/*</li>
</ul>
<p>On peut aussi inclure les éventuels répertoires des plugins, et toutes autres ressources.</p>
<p>On se crée un fichier <code>php.ini</code> :</p>
<pre class="brush:text">
; Server side compression
zlib.output_compression=on
zlib.output_compression_level=9
; tous les fichiers seront parsés par le script headers.php afin d'envoyer le bon Content-Type
auto_prepend_file=/kunden/homepages/25/d3506178849/htdocs/clickandbuilds/WordPress/headers.php
</pre>
<p>On se crée aussi un fichier <code>.htaccess</code> en y mettant :</p>
<pre class="brush:text">
AddType x-mapp-php5 .html .htm .css .js .php
</pre>
<p>Pour que la compression fonctionne sans créer de problème, il va falloir envoyer ces deux fichiers <strong>dans chaque répertoire</strong> des ressources que l&#8217;on souhaite compresser. Par exemple dans <code>/wp-content/themes/votretheme/js/</code> pour que les fichiers JS du thème soient impactés.</p>
<p>Vous pouvez aussi mettre <code>php.ini</code> à la racine de votre site web, <strong>mais sans utiliser</strong> le <code>.htaccess</code> de ci-dessus afin d&#8217;éviter des problèmes avec l&#8217;admin.</p>
<h2>On teste</h2>
<p>Enfin vous pouvez tester si tout est correct grâce à <code>curl</code> sous Linux :<br />
<code>curl -I -H 'Accept-Encoding: gzip,deflate' --head http://votresite/votreressource.js</code></p>
<p>Vous devriez voir apparaitre :</p>
<pre class="brush:plain">
X-Homemade-Compression: OK
Content-Encoding: gzip
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kodono.info/wordpress/2015/03/06/activer-la-compression-gzip-deflate-sur-1and1-astuce/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
	</channel>
</rss>
